HEX
Server: Apache
System: Linux webd004.cluster130.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64
User: frenchy (106757)
PHP: 7.4.33
Disabled: _dyuweyrj4,_dyuweyrj4r,dl
Upload Files
File: /home/frenchy/www/french-american.org/releases/20210414070604Z/node_modules/uglyfly-js/lib/parse.js
/***********************************************************************
  Copyright 2014      (c) Saair Quaderi <saair.quaderi@gmail.com>
  Copyright 2012-2013 (c) Mihai Bazon <mihai.bazon@gmail.com>

  UglyflyJS sourcecode can be found here:
    https://github.com/quaderi/uglyflyjs

  UglyflyJS (by Saair) is a fork of UglifyJS2 (by Mihai Bazon)
  Both libraries are released under the BSD 2-Clause License.
 ***********************************************************************/

/*globals define, module, require */

((typeof define === "function") ? define :
        function () { "use strict"; require('./nd').apply(module, arguments); })(
    "parse",
    [
        "defaults",
        "find_if",
        "js_error",
        "precedence",
        "predicates",
        "is"
    ],
    function (defaults, find_if, js_error, precedence, predicates, is) {
        "use strict";
        /*jslint nomen: true, plusplus: true */

        function tokenizer(AST, $TEXT, filename, html5_comments) {

            var S = {
                text: $TEXT.replace(/\uFEFF/g, ''),
                filename: filename,
                pos: 0,
                tokpos: 0,
                line: 1,
                tokline: 0,
                col: 0,
                tokcol: 0,
                newline_before: false,
                regex_allowed: false,
                comments_before: []
            },
                prev_was_dot = false,
                next_token,
                read_string,
                skip_multiline_comment,
                read_regexp,
                EX_EOF = {};

            function peek() {
                return S.text.charAt(S.pos);
            }

            function next(signal_eof, in_string) {
                var ch = S.text.charAt(S.pos);
                ++S.pos;
                if (signal_eof && !ch) {
                    throw EX_EOF;
                }
                switch (ch) {
                case "\r":
                case "\n":
                case "\u2028":
                case "\u2029":
                    S.newline_before = S.newline_before || !in_string;
                    ++S.line;
                    S.col = 0;
                    if (!in_string && (ch === "\r") && (peek() === "\n")) {
                        // treat a \r\n sequence as a single \n
                        ++S.pos;
                        ch = "\n";
                    }
                    break;
                default:
                    ++S.col;
                    break;
                }
                return ch;
            }

            function forward(i) {
                while (i > 0) {
                    next();
                    --i;
                }
            }

            function looking_at(str) {
                return S.text.substr(S.pos, str.length) === str;
            }

            function find(what, signal_eof) {
                var pos = S.text.indexOf(what, S.pos);
                if (signal_eof && pos === -1) {
                    throw EX_EOF;
                }
                return pos;
            }

            function start_token() {
                S.tokline = S.line;
                S.tokcol = S.col;
                S.tokpos = S.pos;
            }

            function token(type, value, is_comment) {
                var ret,
                    i,
                    len;
                S.regex_allowed = ((type === "operator" && !predicates.UNARY_POSTFIX(value)) ||
                                   (type === "keyword" && predicates.KEYWORDS_BEFORE_EXPRESSION(value)) ||
                                   (type === "punc" && predicates.PUNC_BEFORE_EXPRESSION(value)));
                prev_was_dot = (type === "punc" && value === ".");
                ret = {
                    type: type,
                    value: value,
                    line: S.tokline,
                    col: S.tokcol,
                    pos: S.tokpos,
                    endline: S.line,
                    endcol: S.col,
                    endpos: S.pos,
                    nlb: S.newline_before,
                    file: filename
                };
                if (!is_comment) {
                    ret.comments_before = S.comments_before;
                    S.comments_before = [];
                    // make note of any newlines in the comments that came before
                    for (i = 0, len = ret.comments_before.length; i < len; ++i) {
                        ret.nlb = ret.nlb || ret.comments_before[i].nlb;
                    }
                }
                S.newline_before = false;
                return new AST.Token(ret);
            }

            function skip_whitespace() {
                while (predicates.WHITESPACE_CHARS(peek())) {
                    next();
                }
            }

            function read_while(pred) {
                var ret = "",
                    ch = peek(),
                    i = 0;
                while (ch && pred(ch, i)) {
                    ret += next();
                    ch = peek();
                    ++i;
                }
                return ret;
            }

            function parse_error(err) {
                js_error(err, filename, S.tokline, S.tokcol, S.tokpos);
            }

            function parse_js_number(num) {
                if (/^0x[0-9a-f]+$/i.test(num)) { //hex
                    return parseInt(num.substr(2), 16);
                }
                if (/^0[0-7]+$/.test(num)) { //oct
                    return parseInt(num.substr(1), 8);
                }
                if (/^\d*\.?\d*(?:e[+\-]?\d*(?:\d\.?|\.?\d)\d*)?$/i.test(num)) { //dec
                    return parseFloat(num);
                }
            }

            function read_num(prefix) {
                var has_e = false,
                    after_e = false,
                    has_x = false,
                    has_dot = (prefix === "."),
                    valid,
                    num = read_while(function (ch, i) {
                        var code = ch.charCodeAt(0);
                        switch (code) {
                        case 120:
                        case 88: // xX
                            if (has_x) {
                                return false;
                            }
                            has_x = true;
                            return true;
                        case 101:
                        case 69: // eE
                            if (has_x) {
                                return true;
                            }
                            if (has_e) {
                                return false;
                            }
                            has_e = after_e = true;
                            return true;
                        case 45: // -
                            return after_e || (i === 0 && !prefix);
                        case 43: // +
                            return after_e;
                        case 46: // .
                            after_e = false;
                            if (!has_dot && !has_x && !has_e) {
                                has_dot = true;
                                return true;
                            }
                            return false;
                        default:
                            after_e = false;
                            return is.alphanumeric_char(code);
                        }
                    });
                if (prefix) {
                    num = prefix + num;
                }
                valid = parse_js_number(num);
                if (!isNaN(valid)) {
                    return token("num", valid);
                }
                parse_error("Invalid syntax: " + num);
            }

            function hex_bytes(n) {
                /*jslint bitwise: true */
                var num = 0,
                    digit;
                while (n > 0) {
                    digit = parseInt(next(true), 16);
                    if (isNaN(digit)) {
                        parse_error("Invalid hex-character pattern in string");
                    }
                    num = (num << 4) | digit;
                    --n;
                }
                return num;
            }

            function read_escaped_char(in_string) {
                var ch = next(true, in_string);
                switch (ch.charCodeAt(0)) {
                case 110:
                    return "\n";
                case 114:
                    return "\r";
                case 116:
                    return "\t";
                case 98:
                    return "\b";
                case 118:
                    return "\u000b"; // \v
                case 102:
                    return "\f";
                case 48:
                    return String.fromCharCode(0);
                case 120:
                    return String.fromCharCode(hex_bytes(2)); // \x
                case 117:
                    return String.fromCharCode(hex_bytes(4)); // \u
                case 10:
                    return ""; // newline
                default:
                    return ch;
                }
            }

            function with_eof_error(eof_error, cont) {
                return function (x) {
                    try {
                        return cont(x);
                    } catch (ex) {
                        if (ex === EX_EOF) {
                            parse_error(eof_error);
                        } else {
                            throw ex;
                        }
                    }
                };
            }


            function read_escaped_string(signal_eof) {
                // read OctalEscapeSequence (XXX: deprecated if "strict mode")
                // https://github.com/mishoo/UglifyJS/issues/178
                var octal_str = "",
                    reading_octal,
                    ch;

                do {
                    ch = peek();
                    reading_octal = (ch &&
                        (ch >= "0") &&
                        (ch <= "7") &&
                        ((octal_str.length < 2) ||
                            ((octal_str.length === 2) &&
                                (octal_str[0] < "4"))));
                    if (reading_octal) {
                        octal_str += ch;
                        next(signal_eof);
                    }
                } while (reading_octal);
                return octal_str.length ? String.fromCharCode(parseInt(octal_str, 8)) : read_escaped_char(true);
            }

            read_string = with_eof_error("Unterminated string constant", function (quote_char) {
                var quote = next(),
                    str_val = "",
                    str,
                    end_of_string = false,
                    tok;
                do {
                    str = next(true);
                    if (str === "\\") {
                        str_val += read_escaped_string(true);
                    } else if (str !== quote) {
                        str_val += str;
                    } else {
                        end_of_string = true;
                    }
                } while (!end_of_string);
                tok = token("string", str_val);
                tok.quote = quote_char;
                return tok;
            });

            function skip_line_comment(type) {
                var regex_allowed = S.regex_allowed,
                    i = find("\n"),
                    ret;
                if (i === -1) {
                    ret = S.text.substr(S.pos);
                    S.pos = S.text.length;
                } else {
                    ret = S.text.substring(S.pos, i);
                    S.pos = i;
                }
                S.col = S.tokcol + (S.pos - S.tokpos);
                S.comments_before.push(token(type, ret, true));
                S.regex_allowed = regex_allowed;
                return next_token();
            }

            skip_multiline_comment = with_eof_error("Unterminated multiline comment", function () {
                var regex_allowed = S.regex_allowed,
                    i = find("*/", true),
                    text = S.text.substring(S.pos, i),
                    a = text.split("\n"),
                    n = a.length,
                    nlb;
                // update stream position
                S.pos = i + 2;
                S.line += n - 1;
                if (n > 1) {
                    S.col = a[n - 1].length;
                } else {
                    S.col += a[n - 1].length;
                }
                S.col += 2;
                nlb = S.newline_before = S.newline_before || text.indexOf("\n") >= 0;
                S.comments_before.push(token("comment2", text, true));
                S.regex_allowed = regex_allowed;
                S.newline_before = nlb;
                return next_token();
            });

            function read_name() {
                var backslash = false,
                    name = "",
                    ch = peek(),
                    escaped = false,
                    hex;
                while (ch) {
                    if (!backslash) {
                        if (ch === "\\") {
                            escaped = backslash = true;
                            next();
                        } else if (is.identifier_char(ch)) {
                            name += next();
                        } else {
                            break;
                        }
                    } else {
                        if (ch !== "u") {
                            parse_error("Expecting UnicodeEscapeSequence -- uXXXX");
                        }
                        ch = read_escaped_char();
                        if (!is.identifier_char(ch)) {
                            parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier");
                        }
                        name += ch;
                        backslash = false;
                    }
                    ch = peek();
                }
                if (predicates.KEYWORDS(name) && escaped) {
                    hex = name.charCodeAt(0).toString(16).toUpperCase();
                    name = "\\u" + "0000".substr(hex.length) + hex + name.slice(1);
                }
                return name;
            }

            read_regexp = with_eof_error("Unterminated regular expression", function (regexp) {
                var prev_backslash = false,
                    in_class = false,
                    mods,
                    ch = next(true);
                while (ch) {
                    if (prev_backslash) {
                        regexp += "\\" + ch;
                        prev_backslash = false;
                    } else if (ch === "\\") {
                        prev_backslash = true;
                    } else if (ch === "[") {
                        in_class = true;
                        regexp += ch;
                    } else if (in_class) {
                        if (ch === "]") {
                            in_class = false;
                        }
                        regexp += ch;
                    } else if (ch !== "/") {
                        regexp += ch;
                    } else {
                        break;
                    }
                    ch = next(true);
                }
                mods = read_name();
                return token("regexp", new RegExp(regexp, mods));
            });

            function read_operator(prefix) {
                function grow(op) {
                    var bigger;
                    if (!peek()) {
                        return op;
                    }
                    bigger = op + peek();
                    if (predicates.OPERATORS(bigger)) {
                        next();
                        return grow(bigger);
                    }
                    return op;
                }
                return token("operator", grow(prefix || next()));
            }

            function handle_slash() {
                next();
                switch (peek()) {
                case "/":
                    next();
                    return skip_line_comment("comment1");
                case "*":
                    next();
                    return skip_multiline_comment();
                }
                if (S.regex_allowed) {
                    return read_regexp("");
                }
                return read_operator("/");
            }

            function handle_dot() {
                next();
                if (is.digit(peek().charCodeAt(0))) {
                    return read_num(".");
                }
                return token("punc", ".");
            }

            function read_word() {
                var word = read_name();
                if (prev_was_dot) {
                    return token("name", word);
                }
                if (predicates.KEYWORDS_ATOM(word)) {
                    return token("atom", word);
                }
                if (!predicates.KEYWORDS(word)) {
                    return token("name", word);
                }
                if (predicates.OPERATORS(word)) {
                    return token("operator", word);
                }
                return token("keyword", word);
            }

            next_token = function (force_regexp) {
                var ch,
                    code;
                if (force_regexp) {
                    return read_regexp(force_regexp);
                }
                skip_whitespace();
                start_token();
                if (html5_comments) {
                    if (looking_at("<!--")) {
                        forward(4);
                        return skip_line_comment("comment3");
                    }
                    if (looking_at("-->") && S.newline_before) {
                        forward(3);
                        return skip_line_comment("comment4");
                    }
                }
                ch = peek();
                if (!ch) {
                    return token("eof");
                }
                code = ch.charCodeAt(0);
                switch (code) {
                case 34:
                case 39:
                    return read_string(ch);
                case 46:
                    return handle_dot();
                case 47:
                    return handle_slash();
                }
                if (is.digit(code)) {
                    return read_num();
                }
                if (predicates.PUNC_CHARS(ch)) {
                    return token("punc", next());
                }
                if (predicates.OPERATOR_CHARS(ch)) {
                    return read_operator();
                }
                if (code === 92 || is.identifier_start(code)) {
                    return read_word();
                }
                parse_error("Unexpected character '" + ch + "'");
            };

            next_token.context = function (nc) {
                if (nc) {
                    S = nc;
                }
                return S;
            };

            return next_token;

        }

        /* -----[ Parser ]----- */

        function parse(AST, $TEXT, options) {
            var S,
                statement,
                function_,
                var_,
                const_,
                new_,
                expr_atom,
                array_,
                object_,
                subscripts,
                maybe_unary,
                expr_op,
                maybe_conditional,
                maybe_assign,
                expression;

            options = defaults(options, {
                strict: false,
                filename: null,
                toplevel: null,
                expression: false,
                html5_comments: true,
                bare_returns: false
            });

            S = {
                input: (typeof $TEXT === "string" ? tokenizer(AST, $TEXT, options.filename, options.html5_comments) : $TEXT),
                token: null,
                prev: null,
                peeked: null,
                in_function: 0,
                in_directives: true,
                in_loop: 0,
                labels: []
            };


            function token_is(type, value) {
                return is.token(S.token, type, value);
            }

            function peek() {
                if (!S.peeked) {
                    S.peeked = S.input();
                }
                return S.peeked;
            }

            function next() {
                S.prev = S.token;
                if (S.peeked) {
                    S.token = S.peeked;
                    S.peeked = null;
                } else {
                    S.token = S.input();
                }
                S.in_directives = S.in_directives && (
                    S.token.type === "string" || token_is("punc", ";")
                );
                return S.token;
            }
            S.token = next();

            function prev() {
                return S.prev;
            }

            function croak(msg, line, col, pos) {
                var ctx = S.input.context();
                line = (line === 0) ? 0 : line || ctx.tokline;
                col = (col === 0) ? 0 : col || ctx.tokcol;
                pos = (pos === 0) ? 0 : pos || ctx.tokpos;

                js_error(msg, ctx.filename, line, col, pos);
            }

            function token_error(token, msg) {
                croak(msg, token.line, token.col);
            }

            function unexpected() {
                var token = S.token;
                token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");
            }

            function expect_token(type, val) {
                var token = S.token;
                if (token_is(type, val)) {
                    return next();
                }
                token_error(token, "Unexpected token " + token.type + " «" + token.value + "»" + ", expected " + type + " «" + val + "»");
            }

            function expect(punc) {
                return expect_token("punc", punc);
            }

            function can_insert_semicolon() {
                return !options.strict && (
                    S.token.nlb || token_is("eof") || token_is("punc", "}")
                );
            }

            function semicolon() {
                if (token_is("punc", ";")) {
                    next();
                } else if (!can_insert_semicolon()) {
                    unexpected();
                }
            }

            function parenthesised() {
                var exp;
                expect("(");
                exp = expression(true);
                expect(")");
                return exp;
            }

            function embed_tokens(parser) {
                return function () {
                    var start = S.token,
                        expr = parser(),
                        end = prev();
                    expr.start = start;
                    expr.end = end;
                    return expr;
                };
            }

            function handle_regexp() {
                if (token_is("operator", "/") || token_is("operator", "/=")) {
                    S.peeked = null;
                    S.token = S.input(S.token.value.substr(1)); // force regexp
                }
            }

            function simple_statement(tmp) {
                tmp = expression(true);
                semicolon();
                return new AST.SimpleStatement({
                    body: tmp
                });
            }

            function _make_symbol(SymbolType) {
                var token = S.token,
                    name = token.value,
                    obj = {
                        name: String(name),
                        start: token,
                        end: token
                    };
                if (name === "this") {
                    SymbolType = AST.This;
                }
                return new SymbolType(obj);
            }

            function as_symbol(type, noerror) {
                var sym;
                if (!token_is("name")) {
                    if (!noerror) {
                        croak("Name expected");
                    }
                    return null;
                }
                sym = _make_symbol(type);
                next();
                return sym;
            }


            function break_cont(SymbolType) {
                var label = null,
                    ldef,
                    stat;
                if (!can_insert_semicolon()) {
                    label = as_symbol(AST.LabelRef, true);
                }
                if (label) {
                    ldef = find_if(function (l) {
                        return (l.name === label.name);
                    }, S.labels);
                    if (!ldef) {
                        croak("Undefined label " + label.name);
                    }
                    label.thedef = ldef;
                } else if (!S.in_loop) {
                    croak(SymbolType.TYPE + " not inside a loop or switch");
                }
                semicolon();
                stat = new SymbolType({
                    label: label
                });
                if (ldef) {
                    ldef.references.push(stat);
                }
                return stat;
            }

            function in_loop(cont) {
                var ret;
                ++S.in_loop;
                ret = cont();
                --S.in_loop;
                return ret;
            }

            function regular_for(init) {
                var test,
                    step;
                expect(";");
                test = token_is("punc", ";") ? null : expression(true);
                expect(";");
                step = token_is("punc", ")") ? null : expression(true);
                expect(")");
                return new AST.For({
                    init: init,
                    condition: test,
                    step: step,
                    body: in_loop(statement)
                });
            }

            function for_in(init) {
                var lhs = (init instanceof AST.Var) ? init.definitions[0].name : null,
                    obj = expression(true);
                expect(")");
                return new AST.ForIn({
                    init: init,
                    name: lhs,
                    object: obj,
                    body: in_loop(statement)
                });
            }

            function for_() {
                var init = null;
                expect("(");
                if (!token_is("punc", ";")) {
                    if (token_is("keyword", "var")) {
                        next();
                        init = var_(true);
                    } else {
                        init = expression(true, true);
                    }
                    if (token_is("operator", "in")) {
                        if ((init instanceof AST.Var) && (init.definitions.length > 1)) {
                            croak("Only one variable declaration allowed in for..in loop");
                        }
                        next();
                        return for_in(init);
                    }
                }
                return regular_for(init);
            }

            function labeled_statement() {
                var label = as_symbol(AST.Label),
                    stat;
                if (find_if(function (l) {
                        return l.name === label.name;
                    },
                        S.labels)) {
                    // ECMA-262, 12.12: An ECMAScript program is considered
                    // syntactically incorrect if it contains a
                    // LabelledStatement that is enclosed by a
                    // LabelledStatement with the same Identifier as label.
                    croak("Label " + label.name + " defined twice");
                }
                expect(":");
                S.labels.push(label);
                stat = statement();
                S.labels.pop();
                if (!(stat instanceof AST.IterationStatement)) {
                    // check for `continue` that refers to this label.
                    // those should be reported as syntax errors.
                    // https://github.com/mishoo/UglifyJS2/issues/287
                    label.references.forEach(function (ref) {
                        if (ref instanceof AST.Continue) {
                            ref = ref.label.start;
                            croak("Continue label `" + label.name + "` refers to non-IterationStatement.",
                                  ref.line, ref.col, ref.pos);
                        }
                    });
                }
                return new AST.LabeledStatement({
                    body: stat,
                    label: label
                });
            }

            function if_() {
                var cond = parenthesised(),
                    body = statement(),
                    belse = null;
                if (token_is("keyword", "else")) {
                    next();
                    belse = statement();
                }
                return new AST.If({
                    condition: cond,
                    body: body,
                    alternative: belse
                });
            }

            function block_() {
                var a = [];
                expect("{");
                while (!token_is("punc", "}")) {
                    if (token_is("eof")) {
                        unexpected();
                    }
                    a.push(statement());
                }
                next();
                return a;
            }

            function try_() {
                var body = block_(),
                    bcatch = null,
                    bfinally = null,
                    start,
                    name;
                if (token_is("keyword", "catch")) {
                    start = S.token;
                    next();
                    expect("(");
                    name = as_symbol(AST.SymbolCatch);
                    expect(")");
                    bcatch = new AST.Catch({
                        start: start,
                        argname: name,
                        body: block_(),
                        end: prev()
                    });
                }
                if (token_is("keyword", "finally")) {
                    start = S.token;
                    next();
                    bfinally = new AST.Finally({
                        start: start,
                        body: block_(),
                        end: prev()
                    });
                }
                if (!bcatch && !bfinally) {
                    croak("Missing catch/finally blocks");
                }
                return new AST.Try({
                    body: body,
                    bcatch: bcatch,
                    bfinally: bfinally
                });
            }

            function switch_body_() {
                var a = [],
                    cur = null,
                    branch = null,
                    tmp;
                expect("{");
                while (!token_is("punc", "}")) {
                    if (token_is("eof")) {
                        unexpected();
                    }
                    if (token_is("keyword", "case")) {
                        if (branch) {
                            branch.end = prev();
                        }
                        cur = [];
                        tmp = S.token;
                        next();
                        branch = new AST.Case({
                            start: tmp,
                            expression: expression(true),
                            body: cur
                        });
                        a.push(branch);
                        expect(":");
                    } else if (token_is("keyword", "default")) {
                        if (branch) {
                            branch.end = prev();
                        }
                        cur = [];
                        tmp = S.token;
                        next();
                        expect(":");
                        branch = new AST.Default({
                            start: tmp,
                            body: cur
                        });
                        a.push(branch);
                    } else {
                        if (!cur) {
                            unexpected();
                        }
                        cur.push(statement());
                    }
                }
                if (branch) {
                    branch.end = prev();
                }
                next();
                return a;
            }

            statement = embed_tokens(function () {
                var tmp,
                    dir,
                    stat,
                    body;
                handle_regexp();
                switch (S.token.type) {
                case "string":
                    dir = S.in_directives;
                    stat = simple_statement();
                    // XXXv2: decide how to fix directives
                    if (dir && (stat.body instanceof AST.String) && !token_is("punc", ",")) {
                        return new AST.Directive({
                            start: stat.body.start,
                            end: stat.body.end,
                            quote: stat.body.quote,
                            value: stat.body.value
                        });
                    }
                    return stat;
                case "num":
                case "regexp":
                case "operator":
                case "atom":
                    return simple_statement();

                case "name":
                    return is.token(peek(), "punc", ":") ? labeled_statement() : simple_statement();

                case "punc":
                    switch (S.token.value) {
                    case "{":
                        return new AST.BlockStatement({
                            start: S.token,
                            body: block_(),
                            end: prev()
                        });
                    case "[":
                    case "(":
                        return simple_statement();
                    case ";":
                        next();
                        return new AST.EmptyStatement();
                    default:
                        unexpected();
                        break;
                    }
                    break;

                case "keyword":
                    tmp = S.token.value;
                    next();
                    switch (tmp) {
                    case "break":
                        return break_cont(AST.Break);

                    case "continue":
                        return break_cont(AST.Continue);

                    case "debugger":
                        semicolon();
                        return new AST.Debugger();

                    case "do":
                        body = in_loop(statement);
                        expect_token("keyword", "while");
                        tmp = parenthesised();
                        semicolon();
                        return new AST.Do({
                            body: body,
                            condition: tmp
                        });

                    case "while":
                        return new AST.While({
                            condition: parenthesised(),
                            body: in_loop(statement)
                        });

                    case "for":
                        return for_();

                    case "function":
                        return function_(AST.Defun);

                    case "if":
                        return if_();

                    case "return":
                        tmp = null;
                        if (!S.in_function && !options.bare_returns) {
                            croak("'return' outside of function");
                        }
                        if (token_is("punc", ";")) {
                            next();
                        } else if (!can_insert_semicolon()) {
                            tmp = expression(true);
                            semicolon();
                        }

                        return new AST.Return({
                            value: tmp
                        });

                    case "switch":
                        return new AST.Switch({
                            expression: parenthesised(),
                            body: in_loop(switch_body_)
                        });

                    case "throw":
                        if (S.token.nlb) {
                            croak("Illegal newline after 'throw'");
                        }
                        tmp = expression(true);
                        semicolon();
                        return new AST.Throw({
                            value: tmp
                        });

                    case "try":
                        return try_();

                    case "var":
                        tmp = var_();
                        semicolon();
                        return tmp;

                    case "const":
                        tmp = const_();
                        semicolon();
                        return tmp;

                    case "with":
                        return new AST.With({
                            expression: parenthesised(),
                            body: statement()
                        });

                    default:
                        unexpected();
                        break;
                    }
                    break;
                }
            });

            function_ = function (FunctionType) {
                var in_statement = (FunctionType === AST.Defun),
                    name = null;
                if (token_is("name")) {
                    name = as_symbol(in_statement ? AST.SymbolDefun : AST.SymbolLambda);
                }
                if (in_statement && !name) {
                    unexpected();
                }
                expect("(");
                return new FunctionType({
                    name: name,
                    argnames: (function (first, a) {
                        while (!token_is("punc", ")")) {
                            if (first) {
                                first = false;
                            } else {
                                expect(",");
                            }
                            a.push(as_symbol(AST.SymbolFunarg));
                        }
                        next();
                        return a;
                    }(true, [])),
                    body: (function (loop, labels) {
                        var a;
                        ++S.in_function;
                        S.in_directives = true;
                        S.in_loop = 0;
                        S.labels = [];
                        a = block_();
                        --S.in_function;
                        S.in_loop = loop;
                        S.labels = labels;
                        return a;
                    }(S.in_loop, S.labels))
                });
            };

            function vardefs(no_in, in_const) {
                var a = [],
                    name,
                    val;
                while (true) {
                    val = null;
                    name = as_symbol(in_const ? AST.SymbolConst : AST.SymbolVar);
                    if (token_is("operator", "=")) {
                        next();
                        val = expression(false, no_in);
                    }
                    a.push(new AST.VarDef({
                        start: S.token,
                        name: name,
                        value: val,
                        end: prev()
                    }));
                    if (!token_is("punc", ",")) {
                        break;
                    }
                    next();
                }
                return a;
            }


            var_ = function (no_in) {
                return new AST.Var({
                    start: prev(),
                    definitions: vardefs(no_in, false),
                    end: prev()
                });
            };

            const_ = function () {
                return new AST.Const({
                    start: prev(),
                    definitions: vardefs(false, true),
                    end: prev()
                });
            };

            function expr_list(closing, allow_trailing_comma, allow_empty) {
                var first = true,
                    a = [];
                while (!token_is("punc", closing)) {
                    if (first) {
                        first = false;
                    } else {
                        expect(",");
                    }
                    if (allow_trailing_comma && token_is("punc", closing)) {
                        break;
                    }
                    if (token_is("punc", ",") && allow_empty) {
                        a.push(new AST.Hole({
                            start: S.token,
                            end: S.token
                        }));
                    } else {
                        a.push(expression(false));
                    }
                }
                next();
                return a;
            }

            new_ = function () {
                var start = S.token,
                    newexp,
                    args;
                expect_token("operator", "new");
                newexp = expr_atom(false);
                if (token_is("punc", "(")) {
                    next();
                    args = expr_list(")");
                } else {
                    args = [];
                }
                return subscripts(new AST.New({
                    start: start,
                    expression: newexp,
                    args: args,
                    end: prev()
                }), true);
            };

            function as_atom_node() {
                var token = S.token,
                    ret;
                switch (token.type) {
                case "name":
                case "keyword":
                    ret = _make_symbol(AST.SymbolRef);
                    break;
                case "num":
                    ret = new AST.Number({
                        start: token,
                        end: token,
                        value: token.value
                    });
                    break;
                case "string":
                    ret = new AST.String({
                        start: token,
                        end: token,
                        value: token.value,
                        quote: token.quote
                    });
                    break;
                case "regexp":
                    ret = new AST.RegExp({
                        start: token,
                        end: token,
                        value: token.value
                    });
                    break;
                case "atom":
                    switch (token.value) {
                    case "false":
                        ret = new AST.False({
                            start: token,
                            end: token
                        });
                        break;
                    case "true":
                        ret = new AST.True({
                            start: token,
                            end: token
                        });
                        break;
                    case "null":
                        ret = new AST.Null({
                            start: token,
                            end: token
                        });
                        break;
                    }
                    break;
                }
                next();
                return ret;
            }

            expr_atom = function (allow_calls) {
                var start,
                    func,
                    ex;
                if (token_is("operator", "new")) {
                    return new_();
                }
                start = S.token;
                if (token_is("punc")) {
                    switch (start.value) {
                    case "(":
                        next();
                        ex = expression(true);
                        ex.start = start;
                        ex.end = S.token;
                        expect(")");
                        return subscripts(ex, allow_calls);
                    case "[":
                        return subscripts(array_(), allow_calls);
                    case "{":
                        return subscripts(object_(), allow_calls);
                    }
                    unexpected();
                }
                if (token_is("keyword", "function")) {
                    next();
                    func = function_(AST.Function);
                    func.start = start;
                    func.end = prev();
                    return subscripts(func, allow_calls);
                }
                switch (S.token.type) {
                case "atom":
                case "num":
                case "string":
                case "regexp":
                case "name":
                    return subscripts(as_atom_node(), allow_calls);
                }
                unexpected();
            };

            function as_property_name() {
                var tmp = S.token;
                next();
                switch (tmp.type) {
                case "num":
                case "string":
                case "name":
                case "operator":
                case "keyword":
                case "atom":
                    return tmp.value;
                default:
                    unexpected();
                    break;
                }
            }

            array_ = embed_tokens(function () {
                expect("[");
                return new AST.Array({
                    elements: expr_list("]", !options.strict, true)
                });
            });

            object_ = embed_tokens(function () {
                var first = true,
                    a = [],
                    start,
                    type,
                    name,
                    wasPushed;
                expect("{");
                while (!token_is("punc", "}")) {
                    if (first) {
                        first = false;
                    } else {
                        expect(",");
                    }
                    if (!options.strict && token_is("punc", "}")) {
                        // allow trailing comma
                        break;
                    }
                    start = S.token;
                    type = start.type;
                    name = as_property_name();
                    wasPushed = false;
                    if (type === "name" && !token_is("punc", ":")) {
                        if (name === "get") {
                            a.push(new AST.ObjectGetter({
                                start: start,
                                key: as_atom_node(),
                                value: function_(AST.Accessor),
                                end: prev()
                            }));
                            wasPushed = true;
                        } else if (name === "set") {
                            a.push(new AST.ObjectSetter({
                                start: start,
                                key: as_atom_node(),
                                value: function_(AST.Accessor),
                                end: prev()
                            }));
                            wasPushed = true;
                        }
                    }
                    if (!wasPushed) {
                        expect(":");
                        a.push(new AST.ObjectKeyVal({
                            start: start,
                            quote : start.quote,
                            key: name,
                            value: expression(false),
                            end: prev()
                        }));
                    }
                }
                next();
                return new AST.Object({ properties: a });
            });

            function as_name() {
                var tmp = S.token;
                next();
                switch (tmp.type) {
                case "name":
                case "operator":
                case "keyword":
                case "atom":
                    return tmp.value;
                default:
                    unexpected();
                    break;
                }
            }


            subscripts = function (expr, allow_calls) {
                var start = expr.start,
                    prop;
                if (token_is("punc", ".")) {
                    next();
                    return subscripts(new AST.Dot({
                        start: start,
                        expression: expr,
                        property: as_name(),
                        end: prev()
                    }), allow_calls);
                }
                if (token_is("punc", "[")) {
                    next();
                    prop = expression(true);
                    expect("]");
                    return subscripts(new AST.Sub({
                        start: start,
                        expression: expr,
                        property: prop,
                        end: prev()
                    }), allow_calls);
                }
                if (allow_calls && token_is("punc", "(")) {
                    next();
                    return subscripts(new AST.Call({
                        start: start,
                        expression: expr,
                        args: expr_list(")"),
                        end: prev()
                    }), true);
                }
                return expr;
            };


            function is_assignable(expr) {
                if (!options.strict) {
                    return true;
                }
                if (expr instanceof AST.This) {
                    return false;
                }
                return (expr instanceof AST.PropAccess || expr instanceof AST.Symbol);
            }

            function make_unary(UnaryOperatorType, op, expr) {
                if ((op === "++" || op === "--") && !is_assignable(expr)) {
                    croak("Invalid use of " + op + " operator");
                }
                return new UnaryOperatorType({
                    operator: op,
                    expression: expr
                });
            }

            maybe_unary = function (allow_calls) {
                var start = S.token,
                    ex,
                    val;
                if (token_is("operator") && predicates.UNARY_PREFIX(start.value)) {
                    next();
                    handle_regexp();
                    ex = make_unary(AST.UnaryPrefix, start.value, maybe_unary(allow_calls));
                    ex.start = start;
                    ex.end = prev();
                    return ex;
                }
                val = expr_atom(allow_calls);
                while (token_is("operator") && predicates.UNARY_POSTFIX(S.token.value) && !S.token.nlb) {
                    val = make_unary(AST.UnaryPostfix, S.token.value, val);
                    val.start = start;
                    val.end = S.token;
                    next();
                }
                return val;
            };

            expr_op = function (left, min_prec, no_in) {
                var op = token_is("operator") ? S.token.value : null,
                    prec,
                    right;
                if (op === "in" && no_in) {
                    op = null;
                }
                prec = op ? precedence(op) : null;
                if ((prec !== null) && (prec > min_prec)) {
                    next();
                    right = expr_op(maybe_unary(true), prec, no_in);
                    return expr_op(new AST.Binary({
                        start: left.start,
                        left: left,
                        operator: op,
                        right: right,
                        end: right.end
                    }), min_prec, no_in);
                }
                return left;
            };

            function expr_ops(no_in) {
                return expr_op(maybe_unary(true), 0, no_in);
            }

            maybe_conditional = function (no_in) {
                var start = S.token,
                    expr = expr_ops(no_in),
                    yes;
                if (token_is("operator", "?")) {
                    next();
                    yes = expression(false);
                    expect(":");
                    return new AST.Conditional({
                        start: start,
                        condition: expr,
                        consequent: yes,
                        alternative: expression(false, no_in),
                        end: prev()
                    });
                }
                return expr;
            };

            maybe_assign = function (no_in) {
                var start = S.token,
                    left = maybe_conditional(no_in),
                    val = S.token.value;
                if (token_is("operator") && predicates.ASSIGNMENT(val)) {
                    if (is_assignable(left)) {
                        next();
                        return new AST.Assign({
                            start: start,
                            left: left,
                            operator: val,
                            right: maybe_assign(no_in),
                            end: prev()
                        });
                    }
                    croak("Invalid assignment");
                }
                return left;
            };

            expression = function (commas, no_in) {
                var start = S.token,
                    expr = maybe_assign(no_in);
                if (commas && token_is("punc", ",")) {
                    next();
                    return new AST.Seq({
                        start: start,
                        car: expr,
                        cdr: expression(true, no_in),
                        end: peek()
                    });
                }
                return expr;
            };

            if (options.expression) {
                return expression(true);
            }

            return (function () {
                var start = S.token,
                    body = [],
                    end,
                    toplevel;
                while (!token_is("eof")) {
                    body.push(statement());
                }
                end = prev();
                toplevel = options.toplevel;
                if (toplevel) {
                    toplevel.body = toplevel.body.concat(body);
                    toplevel.end = end;
                } else {
                    toplevel = new AST.Toplevel({
                        start: start,
                        body: body,
                        end: end
                    });
                }
                return toplevel;
            }());

        }

        return parse;
    }
);