// jslint.js
// 2017-11-06
// Copyright (c) 2015 Douglas Crockford  (www.JSLint.com)

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// The Software shall be used for Good, not Evil.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

// jslint(source, option_object, global_array) is a function that takes 3
// arguments. The second two arguments are optional.

//      source          A text to analyze, a string or an array of strings.
//      option_object   An object whose keys correspond to option names.
//      global_array    An array of strings containing global variables that
//                      the file is allowed readonly access.

// jslint returns an object containing its results. The object contains a lot
// of valuable information. It can be used to generate reports. The object
// contains:

//      directives: an array of directive comment tokens.
//      edition: the version of JSLint that did the analysis.
//      exports: the names exported from the module.
//      froms: an array of strings representing each of the imports.
//      functions: an array of objects that represent all of the functions
//              declared in the file.
//      global: an object representing the global object. Its .context property
//              is an object containing a property for each global variable.
//      id: "(JSLint)"
//      json: true if the file is a JSON text.
//      lines: an array of strings, the source.
//      module: true if an import or export statement was used.
//      ok: true if no warnings were generated. This is what you want.
//      option: the option argument.
//      property: a property object.
//      stop: true if JSLint was unable to finish. You don't want this.
//      tokens: an array of objects representing the tokens in the file.
//      tree: the token objects arranged in a tree.
//      warnings: an array of warning objects. A warning object can contain:
//          name: "JSLintError"
//          column: A column number in the file.
//          line: A line number in the file.
//          code: A warning code string.
//          message: The warning message string.
//          a: Exhibit A.
//          b: Exhibit B.
//          c: Exhibit C.
//          d: Exhibit D.

// jslint works in several phases. In any of these phases, errors might be
// found. Sometimes JSLint is able to recover from an error and continue
// parsing. In some cases, it cannot and will stop early. If that should happen,
// repair your code and try again.

// Phases:

//      1. If the source is a single string, split it into an array of strings.
//      2. Turn the source into an array of tokens.
//      3. Furcate the tokens into a parse tree.
//      4. Walk the tree, traversing all of the nodes of the tree. It is a
//          recursive traversal. Each node may be processed on the way down
//          (preaction) and on the way up (postaction).
//      5. Check the whitespace between the tokens.

// jslint can also examine JSON text. It decides that a file is JSON text if
// the first token is "[" or "{". Processing of JSON text is much simpler than
// the processing of JavaScript programs. Only the first three phases are
// required.

// WARNING: JSLint will hurt your feelings.

/*jslint bitwise*/

/*property
    a, and, arity, assign, b, bad_assignment_a, bad_directive_a, bad_get,
    bad_module_name_a, bad_option_a, bad_property_a, bad_set, bitwise, block,
    body, browser, c, calls, catch, charAt, charCodeAt, closer, closure, code,
    column, complex, concat, constant, context, couch, create, d, dead,
    default, devel, directive, directives, disrupt, dot, duplicate_a, edition,
    ellipsis, else, empty_block, es6, escape_mega, eval, every, expected_a,
    expected_a_at_b_c, expected_a_b, expected_a_b_from_c_d,
    expected_a_before_b, expected_a_next_at_b, expected_digits_after_a,
    expected_four_digits, expected_identifier_a, expected_line_break_a_b,
    expected_regexp_factor_a, expected_space_a_b, expected_statements_a,
    expected_string_a, expected_type_string_a, exports, expression, extra,
    finally, flag, for, forEach, free, from, froms, fud, fudge, function,
    function_in_loop, functions, g, getset, global, i, id, identifier, import,
    inc, indexOf, infix_in, init, initial, isArray, isFinite, join, json, keys,
    label, label_a, lbp, led, length, level, line, lines, live, loop, m,
    margin, match, maxerr, maxlen, message, misplaced_a, misplaced_directive_a,
    missing_browser, missing_m, module, multivar, naked_block, name, names,
    nested_comment, new, node, not_label_a, nr, nud, number_isNaN, ok, open,
    option, out_of_scope_a, parameters, pop, property, push, qmark, quote,
    redefinition_a_b, replace, required_a_optional_b, reserved_a, right, role,
    search, signature, single, slice, some, sort, split, statement, stop,
    strict, subscript_a, switch, test, this, thru, toString, todo_comment,
    tokens, too_long, too_many, too_many_digits, tree, try, type, u,
    unclosed_comment, unclosed_mega, unclosed_string, undeclared_a,
    unexpected_a, unexpected_a_after_b, unexpected_a_before_b,
    unexpected_at_top_level_a, unexpected_char_a, unexpected_comment,
    unexpected_directive_a, unexpected_expression_a, unexpected_label_a,
    unexpected_parens, unexpected_space_a_b, unexpected_statement_a,
    unexpected_trailing_space, unexpected_typeof_a, uninitialized_a,
    unreachable_a, unregistered_property_a, unsafe, unused_a, use_double,
    use_spaces, use_strict, used, value, var_loop, var_switch, variable,
    warning, warnings, weird_condition_a, weird_expression_a, weird_loop,
    weird_relation_a, white, wrap_assignment, wrap_condition, wrap_immediate,
    wrap_parameter, wrap_regexp, wrap_unary, wrapped, writable, y
*/


// Polyfill Object.assign...
if (typeof Object.assign !== 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 2
      'use strict';
      if (target === null || target === undefined) {
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource !== null && nextSource !== undefined) { 
          for (var nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

var jslint = (function JSLint() {
    "use strict";

    function empty() {

// The empty function produces a new empty object that inherits nothing. This is
// much better than {} because confusions around accidental method names like
// "constructor" are completely avoided.

        return Object.create(null);
    }

    function populate(object, array, value) {

// Augment an object by taking property names from an array of strings.

        array.forEach(function (name) {
            object[name] = value;
        });
    }

    var allowed_option = {

// These are the options that are recognized in the option object or that may
// appear in a /*jslint*/ directive. Most options will have a boolean value,
// usually true. Some options will also predefine some number of global
// variables.

        bitwise: true,
        browser: [
            "Audio",
            "clearInterval",
            "clearTimeout",
            "document",
            "event",
            "FileReader",
            "FormData",
            "history",
            "Image",
            "localStorage",
            "location",
            "name",
            "navigator",
            "Option",
            "screen",
            "sessionStorage",
            "setInterval",
            "setTimeout",
            "Storage",
            "XMLHttpRequest"
        ],
        couch: [
            "emit", "getRow", "isArray", "log", "provides", "registerType",
            "require", "send", "start", "sum", "toJSON"
        ],
        node: [
            "Buffer", "clearImmediate", "clearInterval", "clearTimeout",
            "console", "exports", "module", "process", "querystring",
            "require", "setImmediate", "setInterval", "setTimeout",
            "__dirname", "__filename"
        ],
        devel: [
            "alert", "confirm", "console", "prompt"
        ],
        es6: true,
        eval: true,
        for: true,
        fudge: true,
        getset: true,
        maxerr: 10000,
        maxlen: 10000,
        multivar: true,
        Synchronet: [
		"log", "read", "readln", "write", "write_raw", "writeln",
		"print", "printf", "alert", "prompt", "confirm", "exit",
		"load", "mswait", "yield", "random", "time", "beep",
		"sound", "ctrl", "ascii", "ascii_str", "strip_ctrl",
		"strip_exascii", "truncsp", "truncstr", "lfexpand",
		"wildmatch", "backslash", "file_getname", "file_getext",
		"file_getcase", "file_cfgname", "file_exists",
		"file_remove", "file_removecase", "file_rename",
		"file_copy", "file_backup", "file_isdir", "file_attrib",
		"file_date", "file_size", "file_utime", "file_touch",
		"file_mutex", "file_compare", "directory",
		"dir_freespace", "disk_size", "socket_select", "mkdir",
		"rmdir", "strftime", "format", "html_encode",
		"html_decode", "word_wrap", "quote_msg",
		"rot13_translate", "base64_encode", "base64_decode",
		"crc16_calc", "crc32_calc", "chksum_calc", "md5_calc",
		"resolve_ip", "resolve_host", "netaddr_type",
		"list_named_queues", "flags_str", "login", "fullpath",
		"mkpath", "require", "argc", "argv", "errno",
		"errnostr", "socket_errno", "global", "js", "system",
		"server", "client", "user", "bbs", "console",
		"msg_area", "file_area", "xtrn_area", "MsgBase",
		"File", "Queue", "Socket", "ConnectedSocket", "ListeningSocket", "User", "COM",
		"CryptContext", "CryptKeyset", "CryptCert", "LOG_EMERG",
		"LOG_ALERT", "LOG_CRIT", "LOG_ERR", "LOG_ERROR",
		"LOG_WARNING", "LOG_NOTICE", "LOG_INFO", "LOG_DEBUG",
		"conio", "stdin", "stdout", "stderr", "jsexec_revision",
		"jsdoor_revision"
        ],
        single: true,
        this: true,
        white: true
    };

    var spaceop = {

// This is the set of infix operators that require a space on each side.

        "!=": true,
        "!==": true,
        "%": true,
        "%=": true,
        "&": true,
        "&=": true,
        "&&": true,
        "*": true,
        "*=": true,
        "+=": true,
        "-=": true,
        "/": true,
        "/=": true,
        "<": true,
        "<=": true,
        "<<": true,
        "<<=": true,
        "=": true,
        "==": true,
        "===": true,
//        "=>": true,
        ">": true,
        ">=": true,
        ">>": true,
        ">>=": true,
        ">>>": true,
        ">>>=": true,
        "^": true,
        "^=": true,
        "|": true,
        "|=": true,
        "||": true
    };

    var bitwiseop = {

// These are the bitwise operators.

        "~": true,
        "^": true,
        "^=": true,
        "&": true,
        "&=": true,
        "|": true,
        "|=": true,
        "<<": true,
        "<<=": true,
        ">>": true,
        ">>=": true,
        ">>>": true,
        ">>>=": true
    };

    var opener = {

// The open and close pairs.

        "(": ")",       // paren
        "[": "]",       // bracket
        "{": "}",       // brace
//        "${": "}"       // mega
    };

    var relationop = {

// The relational operators.

        "!=": true,
        "!==": true,
        "==": true,
        "===": true,
        "<": true,
        "<=": true,
        ">": true,
        ">=": true
    };

    var standard = [

// These are the globals that are provided by the language standard.

        "Array", "ArrayBuffer", "Boolean", /*"DataView", */"Date", "decodeURI",
        "decodeURIComponent", "encodeURI", "encodeURIComponent", "Error",
        "EvalError", "Float32Array", "Float64Array", /*"Generator",
        /*"GeneratorFunction", */"Int8Array", "Int16Array", "Int32Array", /*"Intl", */
        "JSON", /*"Map", */"Math", "Number", "Object", "parseInt", "parseFloat",
        /*"Promise", */"Proxy", "RangeError", "ReferenceError", /*"Reflect", */"RegExp",
        /*"Set", */"String", /*"Symbol", */"SyntaxError", /*"System", */"TypeError",
        "Uint8Array", "Uint8ClampedArray", "Uint16Array", "Uint32Array",
        "URIError"/*, "WeakMap"*//*, "WeakSet"*/, "isNaN"
    ];

    var bundle = {

// The bundle contains the raw text messages that are generated by jslint. It
// seems that they are all error messages and warnings. There are no "Atta
// boy!" or "You are so awesome!" messages. There is no positive reinforcement
// or encouragement. This relentless negativity can undermine self-esteem and
// wound the inner child. But if you accept it as sound advice rather than as
// personal criticism, it can make your programs better.

        and: "The '&&' subexpression should be wrapped in parens.",
        bad_assignment_a: "Bad assignment to '{a}'.",
        bad_directive_a: "Bad directive '{a}'.",
        bad_get: "A get function takes no parameters.",
        bad_module_name_a: "Bad module name '{a}'.",
        bad_option_a: "Bad option '{a}'.",
        bad_property_a: "Bad property name '{a}'.",
        bad_set: "A set function takes one parameter.",
        duplicate_a: "Duplicate '{a}'.",
        empty_block: "Empty block.",
        escape_mega: "Unexpected escapement in mega literal.",
        expected_a: "Expected '{a}'.",
        expected_a_at_b_c: "Expected '{a}' at column {b}, not column {c}.",
        expected_a_b: "Expected '{a}' and instead saw '{b}'.",
        expected_a_b_from_c_d: "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
        expected_a_before_b: "Expected '{a}' before '{b}'.",
        expected_a_next_at_b: "Expected '{a}' at column {b} on the next line.",
        expected_digits_after_a: "Expected digits after '{a}'.",
        expected_four_digits: "Expected four digits after '\\u'.",
        expected_identifier_a: "Expected an identifier and instead saw '{a}'.",
        expected_line_break_a_b: "Expected a line break between '{a}' and '{b}'.",
        expected_regexp_factor_a: "Expected a regexp factor and instead saw '{a}'.",
        expected_space_a_b: "Expected one space between '{a}' and '{b}'.",
        expected_statements_a: "Expected statements before '{a}'.",
        expected_string_a: "Expected a string and instead saw '{a}'.",
        expected_type_string_a: "Expected a type string and instead saw '{a}'.",
        function_in_loop: "Don't make functions within a loop.",
        infix_in: "Unexpected 'in'. Compare with undefined, or use the hasOwnProperty method instead.",
        label_a: "'{a}' is a statement label.",
        misplaced_a: "Place '{a}' at the outermost level.",
        misplaced_directive_a: "Place the '/*{a}*/' directive before the first statement.",
        missing_browser: "/*global*/ requires the Assume a browser option.",
        missing_m: "Expected 'm' flag on a multiline regular expression.",
        naked_block: "Naked block.",
        nested_comment: "Nested comment.",
        not_label_a: "'{a}' is not a label.",
        //number_isNaN: "Use Number.isNaN function to compare with NaN.",
        out_of_scope_a: "'{a}' is out of scope.",
        redefinition_a_b: "Redefinition of '{a}' from line {b}.",
        required_a_optional_b: "Required parameter '{a}' after optional parameter '{b}'.",
        reserved_a: "Reserved name '{a}'.",
        subscript_a: "['{a}'] is better written in dot notation.",
        todo_comment: "Unexpected TODO comment.",
        too_long: "Line too long.",
        too_many: "Too many warnings.",
        too_many_digits: "Too many digits.",
        unclosed_comment: "Unclosed comment.",
        unclosed_mega: "Unclosed mega literal.",
        unclosed_string: "Unclosed string.",
        undeclared_a: "Undeclared '{a}'.",
        unexpected_a: "Unexpected '{a}'.",
        unexpected_a_after_b: "Unexpected '{a}' after '{b}'.",
        unexpected_a_before_b: "Unexpected '{a}' before '{b}'.",
        unexpected_at_top_level_a: "Expected '{a}' to be in a function.",
        unexpected_char_a: "Unexpected character '{a}'.",
        unexpected_comment: "Unexpected comment.",
        unexpected_directive_a: "When using modules, don't use directive '/*{a}'.",
        unexpected_expression_a: "Unexpected expression '{a}' in statement position.",
        unexpected_label_a: "Unexpected label '{a}'.",
        unexpected_parens: "Don't wrap function literals in parens.",
        unexpected_space_a_b: "Unexpected space between '{a}' and '{b}'.",
        unexpected_statement_a: "Unexpected statement '{a}' in expression position.",
        unexpected_trailing_space: "Unexpected trailing space.",
        unexpected_typeof_a: "Unexpected 'typeof'. Use '===' to compare directly with {a}.",
        uninitialized_a: "Uninitialized '{a}'.",
        unreachable_a: "Unreachable '{a}'.",
        unregistered_property_a: "Unregistered property name '{a}'.",
        unsafe: "Unsafe character '{a}'.",
        unused_a: "Unused '{a}'.",
        use_double: "Use double quotes, not single quotes.",
        use_spaces: "Use spaces, not tabs.",
        use_strict: "This function needs a \"use strict\" pragma.",
        var_loop: "Don't declare variables in a loop.",
        var_switch: "Don't declare variables in a switch.",
        weird_condition_a: "Weird condition '{a}'.",
        weird_expression_a: "Weird expression '{a}'.",
        weird_loop: "Weird loop.",
        weird_relation_a: "Weird relation '{a}'.",
        wrap_assignment: "Don't wrap assignment statements in parens.",
        wrap_condition: "Wrap the condition in parens.",
        wrap_immediate: (
            "Wrap an immediate function invocation in parentheses to assist "
            + "the reader in understanding that the expression is the result "
            + "of a function, and not the function itself."
        ),
        wrap_parameter: "Wrap the parameter in parens.",
        wrap_regexp: "Wrap this regexp in parens to avoid confusion.",
        wrap_unary: "Wrap the unary expression in parens."
    };

// Regular expression literals:

// supplant {variables}
    var rx_supplant = /\{([^{}]*)\}/g;
// carriage return, carriage return linefeed, or linefeed
    var rx_crlf = /\n|\r\n?/;
// unsafe characters that are silently deleted by one or more browsers
    var rx_unsafe = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
// identifier
    var rx_identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
    var rx_module = /^[a-zA-Z0-9_$:.@\-\/]+$/;
    var rx_bad_property = /^_|\$|Sync\$|_$/;
// star slash
    var rx_star_slash = /\*\//;
// slash star
    var rx_slash_star = /\/\*/;
// slash star or ending slash
    var rx_slash_star_or_slash = /\/\*|\/$/;
// uncompleted work comment
    var rx_todo = /\b(?:todo|TO\s?DO|HACK)\b/;
// tab
    var rx_tab = /\t/g;
// directive
    var rx_directive = /^(jslint|property|global)\s+(.*)$/;
    var rx_directive_part = /^([a-zA-Z$_][a-zA-Z0-9$_]*)\s*(?::\s*(true|false|[0-9]+)\s*)?(?:,\s*)?(.*)$/;
// token (sorry it is so long)
    //var rx_token = /^((\s+)|([a-zA-Z_$][a-zA-Z0-9_$]*)|[(){}\[\]?,:;'"~`]|=(?:==?|>)?|\.+|[*\/][*\/=]?|\+(?:=|\++)?|-(?:=|-+)?|[\^%]=?|&[&=]?|\|[|=]?|>{1,3}=?|<<?=?|!={0,2}|(0|[1-9][0-9]*))(.*)$/;
    // Synchronet removes mega strings
    var rx_token = /^((\s+)|([a-zA-Z_$][a-zA-Z0-9_$]*)|[(){}\[\]?,:;'"~]|=(?:==?|>)?|\.+|[*\/][*\/=]?|\+(?:=|\++)?|-(?:=|-+)?|[\^%]=?|&[&=]?|\|[|=]?|>{1,3}=?|<<?=?|!={0,2}|(0|[1-9][0-9]*))(.*)$/;
    var rx_digits = /^([0-9]+)(.*)$/;
    var rx_hexs = /^([0-9a-fA-F]+)(.*)$/;
    var rx_octals = /^([0-7]+)(.*)$/;
    var rx_bits = /^([01]+)(.*)$/;
// mega
    // Synchronet removes mega strings
    //var rx_mega = /[`\\]|\$\{/;
// indentation
    var rx_colons = /^(.*)\?([:.]*)$/;
    var rx_dot = /\.$/;
// JSON number
    var rx_JSON_number = /^-?\d+(?:\.\d*)?(?:e[\-+]?\d+)?$/i;
// initial cap
    var rx_cap = /^[A-Z]/;

    function is_letter(string) {
        return (
            (string >= "a" && string <= "z\uffff")
            || (string >= "A" && string <= "Z\uffff")
        );
    }

    function supplant(string, object) {
        return string.replace(rx_supplant, function (found, filling) {
            var replacement = object[filling];
            return (replacement !== undefined)
                ? replacement
                : found;
        });
    }

    var anon = "anonymous"; // The guessed name for anonymous functions.
    var blockage;           // The current block.
    var block_stack;        // The stack of blocks.
    var declared_globals;   // The object containing the global declarations.
    var directives;         // The directive comments.
    var directive_mode;     // true if directives are still allowed.
    var early_stop;         // true if JSLint cannot finish.
    var exports;            // The exported names and values.
    var froms;              // The array collecting all import-from strings.
    var fudge;              // true if the natural numbers start with 1.
    var functionage;        // The current function.
    var functions;          // The array containing all of the functions.
    var global;             // The global object; the outermost context.
    var json_mode;          // true if parsing JSON.
    var lines;              // The array containing source lines.
    var module_mode;        // true if import or export was used.
    var next_token;         // The next token to be examined in the parse.
    var option;             // The options parameter.
    var property;           // The object containing the tallied property names.
//    var mega_mode;          // true if currently parsing a megastring literal.
    var stack;              // The stack of functions.
    var syntax;             // The object containing the parser.
    var token;              // The current token being examined in the parse.
    var token_nr;           // The number of the next token.
    var tokens;             // The array of tokens.
    var tenure;             // The predefined property registry.
    var tree;               // The abstract parse tree.
    var var_mode = 'let';           // "var" if using var; "let" if using let.
    var warnings;           // The array collecting all generated warnings.

// Error reportage functions:

    function artifact(the_token) {

// Return a string representing an artifact.

        if (the_token === undefined) {
            the_token = next_token;
        }
        return (the_token.id === "(string)" || the_token.id === "(number)")
            ? String(the_token.value)
            : the_token.id;
    }

    function artifact_line(the_token) {

// Return the fudged line number of an artifact.

        if (the_token === undefined) {
            the_token = next_token;
        }
        return the_token.line + fudge;
    }

    function artifact_column(the_token) {

// Return the fudged column number of an artifact.

        if (the_token === undefined) {
            the_token = next_token;
        }
        return the_token.from + fudge;
    }

    function warn_at(code, line, column, a, b, c, d) {

// Report an error at some line and column of the program. The warning object
// resembles an exception.

        var warning = {         // ~~
            name: "JSLintError",
            column: column,
            line: line,
            code: code
        };
        if (a !== undefined) {
            warning.a = a;
        }
        if (b !== undefined) {
            warning.b = b;
        }
        if (c !== undefined) {
            warning.c = c;
        }
        if (d !== undefined) {
            warning.d = d;
        }
        warning.message = supplant(bundle[code] || code, warning);
        warnings.push(warning);
        return (
            typeof option.maxerr === "number"
            && warnings.length === option.maxerr
        )   ? stop_at("too_many", line, column)
            : warning;
    }

    function stop_at(code, line, column, a, b, c, d) {

// Same as warn_at, except that it stops the analysis.

        throw warn_at(code, line, column, a, b, c, d);
    }

    function warn(code, the_token, a, b, c, d) {

// Same as warn_at, except the warning will be associated with a specific token.
// If there is already a warning on this token, suppress the new one. It is
// likely that the first warning will be the most meaningful.

        if (the_token === undefined) {
            the_token = next_token;
        }
        if (the_token.warning === undefined) {
            the_token.warning = warn_at(
                code,
                the_token.line,
                the_token.from,
                a || artifact(the_token),
                b,
                c,
                d
            );
            return the_token.warning;
        }
    }

    function stop(code, the_token, a, b, c, d) {

// Similar to warn and stop_at. If the token already had a warning, that
// warning will be replaced with this new one. It is likely that the stopping
// warning will be the more meaningful.

        if (the_token === undefined) {
            the_token = next_token;
        }
        delete the_token.warning;
        throw warn(code, the_token, a, b, c, d);
    }

// Tokenize:

    function tokenize(source) {

// tokenize takes a source and produces from it an array of token objects.
// JavaScript is notoriously difficult to tokenize because of the horrible
// interactions between automatic semicolon insertion, regular expression
// literals, and now megastring literals. JSLint benefits from eliminating
// automatic semicolon insertion and nested megastring literals, which allows
// full tokenization to precede parsing.

// If the source is not an array, then it is split into lines at the
// carriage return/linefeed.

        lines = (Array.isArray(source))
            ? source
            : source.split(rx_crlf);
        tokens = [];

        var char;                   // a popular character
        var column = 0;             // the column number of the next character
        var first;                  // the first token
        var from;                   // the starting column number of the token
        var line = -1;              // the line number of the next character
        var nr = 0;                 // the next token number
        var previous = global;      // the previous token including comments
        var prior = global;         // the previous token excluding comments
//        var mega_from;              // the starting column of megastring
//        var mega_line;              // the starting line of megastring
        var snippet;                // a piece of string
        var source_line;            // the current line source string

        function next_line() {

// Put the next line of source in source_line. If the line contains tabs,
// replace them with spaces and give a warning. Also warn if the line contains
// unsafe characters or is too damn long.

            var at;
            column = 0;
            line += 1;
            source_line = lines[line];
            if (source_line !== undefined) {
                at = source_line.search(rx_tab);
                if (at >= 0) {
                    if (!option.white) {
                        warn_at("use_spaces", line, at + 1);
                    }
                    source_line = source_line.replace(rx_tab, " ");
                }
                at = source_line.search(rx_unsafe);
                if (at >= 0) {
                    warn_at(
                        "unsafe",
                        line,
                        column + at,
                        "U+" + source_line.charCodeAt(at).toString(16)
                    );
                }
                if (option.maxlen && option.maxlen < source_line.length) {
                    warn_at("too_long", line, source_line.length);
                } else if (!option.white && source_line.slice(-1) === " ") {
                    warn_at(
                        "unexpected_trailing_space",
                        line,
                        source_line.length - 1
                    );
                }
            }
            return source_line;
        }

// Most tokens, including the identifiers, operators, and punctuators, can be
// found with a regular expression. Regular expressions cannot correctly match
// regular expression literals, so we will match those the hard way. String
// literals and number literals can be matched by regular expressions, but they
// don't provide good warnings. The functions snip, next_char, prev_char,
// some_digits, and escape help in the parsing of literals.

        function snip() {

// Remove the last character from snippet.

            snippet = snippet.slice(0, -1);
        }

        function next_char(match) {

// Get the next character from the source line. Remove it from the source_line,
// and append it to the snippet. Optionally check that the previous character
// matched an expected value.

            if (match !== undefined && char !== match) {
                return stop_at(
                    (char === "")
                        ? "expected_a"
                        : "expected_a_b",
                    line,
                    column - 1,
                    match,
                    char
                );
            }
            if (source_line) {
                char = source_line.charAt(0);
                source_line = source_line.slice(1);
                snippet += char;
            } else {
                char = "";
                snippet += " ";
            }
            column += 1;
            return char;
        }

        function back_char() {

// Back up one character by moving a character from the end of the snippet to
// the front of the source_line.

            if (snippet) {
                char = snippet.slice(-1);
                source_line = char + source_line;
                column -= 1;
                snip();
            } else {
                char = "";
            }
            return char;
        }

        function some_digits(rx, quiet) {
            var result = source_line.match(rx);
            if (result) {
                char = result[1];
                column += char.length;
                source_line = result[2];
                snippet += char;
            } else {
                char = "";
                if (!quiet) {
                    warn_at(
                        "expected_digits_after_a",
                        line,
                        column,
                        snippet
                    );
                }
            }
            return char.length;
        }

        function escape(extra) {
            switch (next_char("\\")) {
            case "\\":
            case "/":
            case "b":
            case "f":
            case "n":
            case "r":
            case "t":
            case "x":		// Added for Synchronet...
                break;
            case "u":
                if (next_char("u") === "{") {
                    if (json_mode) {
                        warn_at("unexpected_a", line, column - 1, char);
                    }
                    if (some_digits(rx_hexs) > 5) {
                        warn_at("too_many_digits", line, column - 1);
                    }
                    if (next_char() !== "}") {
                        stop_at("expected_a_before_b", line, column, "}", char);
                    }
                    next_char();
                    return;
                }
                back_char();
                if (some_digits(rx_hexs, true) < 4) {
                    warn_at("expected_four_digits", line, column - 1);
                }
                break;
            case "":
                return stop_at("unclosed_string", line, column);
            default:
                if (!extra || extra.indexOf(char) < 0) {
                    warn_at(
                        "unexpected_a_before_b",
                        line,
                        column - 2,
                        "\\",
                        char
                    );
                }
            }
            next_char();
        }

        function make(id, value, identifier) {

// Make the token object and append it to the tokens list.

            var the_token = {
                from: from,
                id: id,
                identifier: !!identifier,
                line: line,
                nr: nr,
                thru: column
            };
            tokens[nr] = the_token;
            nr += 1;

// Directives must appear before the first statement.

            if (id !== "(comment)" && id !== ";") {
                directive_mode = false;
            }

// If the token is to have a value, give it one.

            if (value !== undefined) {
                the_token.value = value;
            }

// If this token is an identifier that touches a preceding number, or
// a "/", comment, or regular expression literal that touches a preceding
// comment or regular expression literal, then give a missing space warning.
// This warning is not suppressed by option.white.

            if (
                previous.line === line
                && previous.thru === from
                && (id === "(comment)" || id === "(regexp)" || id === "/")
                && (previous.id === "(comment)" || previous.id === "(regexp)")
            ) {
                warn(
                    "expected_space_a_b",
                    the_token,
                    artifact(previous),
                    artifact(the_token)
                );
            }
            if (previous.id === "." && id === "(number)") {
                warn("expected_a_before_b", previous, "0", ".");
            }
            if (prior.id === "." && the_token.identifier) {
                the_token.dot = true;
            }

// The previous token is used to detect adjacency problems.

            previous = the_token;

// The prior token is a previous token that was not a comment. The prior token
// is used to disambiguate "/", which can mean division or regular expression
// literal.

            if (previous.id !== "(comment)") {
                prior = previous;
            }
            return the_token;
        }

        function parse_directive(the_comment, body) {

// JSLint recognizes three directives that can be encoded in comments. This
// function processes one item, and calls itself recursively to process the
// next one.

            var result = body.match(rx_directive_part);
            if (result) {
                var allowed;
                var name = result[1];
                var value = result[2];
                switch (the_comment.directive) {
                case "jslint":
                    allowed = allowed_option[name];
                    switch (typeof allowed) {
                    case "boolean":
                    case "object":
                        switch (value) {
                        case "true":
                        case "":
                        case undefined:
                            option[name] = true;
                            if (Array.isArray(allowed)) {
                                populate(declared_globals, allowed, false);
                            }
                            break;
                        case "false":
                            option[name] = false;
                            break;
                        default:
                            warn(
                                "bad_option_a",
                                the_comment,
                                name + ":" + value
                            );
                        }
                        break;
                    case "number":
                        if (Number.isFinite(+value)) {
                            option[name] = +value;
                        } else {
                            warn(
                                "bad_option_a",
                                the_comment,
                                name + ":" + value
                            );
                        }
                        break;
                    default:
                        warn("bad_option_a", the_comment, name);
                    }
                    break;
                case "property":
                    if (tenure === undefined) {
                        tenure = empty();
                    }
                    tenure[name] = true;
                    break;
                case "global":
                    if (value) {
                        warn("bad_option_a", the_comment, name + ":" + value);
                    }
                    declared_globals[name] = false;
                    module_mode = the_comment;
                    break;
                }
                return parse_directive(the_comment, result[3]);
            }
            if (body) {
                return stop("bad_directive_a", the_comment, body);
            }
        }

        function comment(snippet) {

// Make a comment object. Comments are not allowed in JSON text. Comments can
// include directives and notices of incompletion.

            var the_comment = make("(comment)", snippet);
            if (Array.isArray(snippet)) {
                snippet = snippet.join(" ");
            }
            if (!option.devel && rx_todo.test(snippet)) {
                warn("todo_comment", the_comment);
            }
            var result = snippet.match(rx_directive);
            if (result) {
                if (!directive_mode) {
                    //warn_at("misplaced_directive_a", line, from, result[1]);
                } else {
                    the_comment.directive = result[1];
                    parse_directive(the_comment, result[2]);
                }
                directives.push(the_comment);
            }
            return the_comment;
        }

        function regexp() {

// Parse a regular expression literal.

            var multi_mode = false;
            var result;
            var value;

            function quantifier() {

// Match an optional quantifier.

                switch (char) {
                case "?":
                case "*":
                case "+":
                    next_char();
                    break;
                case "{":
                    if (some_digits(rx_digits, true) === 0) {
                        warn_at("expected_a", line, column, "0");
                    }
                    if (next_char() === ",") {
                        some_digits(rx_digits, true);
                        next_char();
                    }
                    next_char("}");
                    break;
                default:
                    return;
                }
                if (char === "?") {
                    next_char("?");
                }
            }

            function subklass() {

// Match a character in a character class.

                switch (char) {
                case "\\":
                    escape("BbDdSsWw-[]^");
                    return true;
                case "[":
                case "]":
                case "/":
                case "^":
                case "-":
                case "":
                    return false;
                /*case "`":
                    if (mega_mode) {
                        warn_at("unexpected_a", line, column, "`");
                    }
                    next_char();
                    return true;*/
                case " ":
                    warn_at("expected_a_b", line, column, "\\u0020", " ");
                    next_char();
                    return true;
                default:
                    next_char();
                    return true;
                }
            }

            function ranges() {

// Match a range of subclasses.

                if (subklass()) {
                    if (char === "-") {
                        next_char("-");
                        if (!subklass()) {
                            return stop_at(
                                "unexpected_a",
                                line,
                                column - 1,
                                "-"
                            );
                        }
                    }
                    return ranges();
                }
            }

            function klass() {

// Match a class.

                next_char("[");
                if (char === "^") {
                    next_char("^");
                }
                (function classy() {
                    ranges();
                    if (char !== "]" && char !== "") {
                        warn_at(
                            "expected_a_before_b",
                            line,
                            column,
                            "\\",
                            char
                        );
                        next_char();
                        return classy();
                    }
                }());
                next_char("]");
            }

            function choice() {

                function group() {

// Match a group that starts with left paren.

                    next_char("(");
                    if (char === "?") {
                        next_char("?");
                        switch (char) {
                        case ":":
                        case "=":
                        case "!":
                            next_char();
                            break;
                        default:
                            next_char(":");
                        }
                    } else if (char === ":") {
                        warn_at("expected_a_before_b", line, column, "?", ":");
                    }
                    choice();
                    next_char(")");
                }

                function factor() {
                    switch (char) {
                    case "[":
                        klass();
                        return true;
                    case "\\":
                        escape("BbDdSsWw^${}[]():=!.-|*+?");
                        return true;
                    case "(":
                        group();
                        return true;
                    case "?":
                    case "+":
                    case "*":
                    case "}":
                    case "{":
                        warn_at("expected_a_before_b", line, column - 1, "\\", char);
                        break;
                    case "/":
                    case "|":
                    case "]":
                    case ")":
                    case "":
                        return false;
                    /*case "`":
                        if (mega_mode) {
                            warn_at("unexpected_a", line, column - 1, "`");
                        }
                        break;*/
                    case " ":
                        warn_at(
                            "expected_a_b",
                            line,
                            column - 1,
                            "\\s",
                            " "
                        );
                        break;
                    case "$":
                        if (source_line.charAt(0) !== "/") {
                            multi_mode = true;
                        }
                        break;
                    case "^":
                        if (snippet !== "^") {
                            multi_mode = true;
                        }
                        break;
                    }
                    next_char();
                    return true;
                }

                function sequence(follow) {
                    if (factor()) {
                        quantifier();
                        return sequence(true);
                    }
                    if (!follow) {
                        warn_at("expected_regexp_factor_a", line, column, char);
                    }
                }

// Match a choice (a sequence that can be followed by | and another choice).

                sequence();
                if (char === "|") {
                    next_char("|");
                    return choice();
                }
            }

// Scan the regexp literal. Give a warning if the first character is = because
// /= looks like a division assignment operator.

            snippet = "";
            next_char();
            if (char === "=") {
                warn_at("expected_a_before_b", line, column, "\\", "=");
            }
            choice();

// Make sure there is a closing slash.

            snip();
            value = snippet;
            next_char("/");

// Process dangling flag letters.

            var allowed = {
                g: true,
                i: true,
                m: true,
                u: true,
                y: true
            };
            var flag = empty();
            (function make_flag() {
                if (is_letter(char)) {
                    if (allowed[char] !== true) {
                        warn_at("unexpected_a", line, column, char);
                    }
                    allowed[char] = false;
                    flag[char] = true;
                    next_char();
                    return make_flag();
                }
            }());
            back_char();
            if (char === "/" || char === "*") {
                return stop_at("unexpected_a", line, from, char);
            }
            result = make("(regexp)", char);
            result.flag = flag;
            result.value = value;
            if (multi_mode && !flag.m) {
                warn_at("missing_m", line, column);
            }
            return result;
        }

        function string(quote) {

// Make a string token.

            var the_token;
            snippet = "";
            next_char();

            return (function next() {
                switch (char) {
                case quote:
                    snip();
                    the_token = make("(string)", snippet);
                    the_token.quote = quote;
                    return the_token;
                case "\\":
                    escape(quote);
                    break;
                case "":
                    return stop_at("unclosed_string", line, column);
                /*case "`":
                    if (mega_mode) {
                        warn_at("unexpected_a", line, column, "`");
                    }
                    next_char("`");
                    break;*/
                default:
                    next_char();
                }
                return next();
            }());
        }

        function frack() {
            if (char === ".") {
                some_digits(rx_digits);
                next_char();
            }
            if (char === "E" || char === "e") {
                next_char();
                if (char !== "+" && char !== "-") {
                    back_char();
                }
                some_digits(rx_digits);
                next_char();
            }
        }

        function number() {
            if (snippet === "0") {
                switch (next_char()) {
                case ".":
                    frack();
                    break;
                case "b":
                    some_digits(rx_bits);
                    next_char();
                    break;
                case "o":
                    some_digits(rx_octals);
                    next_char();
                    break;
                case "x":
                    some_digits(rx_hexs);
                    next_char();
                    break;
                }
            } else {
                next_char();
                frack();
            }

// If the next character after a number is a digit or letter, then something
// unexpected is going on.

            if (
                (char >= "0" && char <= "9")
                || (char >= "a" && char <= "z")
                || (char >= "A" && char <= "Z")
            ) {
                return stop_at(
                    "unexpected_a_after_b",
                    line,
                    column - 1,
                    snippet.slice(-1),
                    snippet.slice(0, -1)
                );
            }
            back_char();
            return make("(number)", snippet);
        }

        function lex() {
            var array;
            var i = 0;
            var j = 0;
            var last;
            var result;
            var the_token;
            if (!source_line) {
                source_line = next_line();
                from = 0;
                return (source_line === undefined) ? make("(end)") : lex();
                    //? (mega_mode)
                        //? stop_at("unclosed_mega", mega_line, mega_from)
                        //? make("(end)")
                    //: lex();
            }
            from = column;
            result = source_line.match(rx_token);

// result[1] token
// result[2] whitespace
// result[3] identifier
// result[4] number
// result[5] rest

            if (!result) {
                return stop_at(
                    "unexpected_char_a",
                    line,
                    column,
                    source_line.charAt(0)
                );
            }

            snippet = result[1];
            column += snippet.length;
            source_line = result[5];

// Whitespace was matched. Call lex again to get more.

            if (result[2]) {
                return lex();
            }

// The token is an identifier.

            if (result[3]) {
                return make(snippet, undefined, true);
            }

// The token is a number.

            if (result[4]) {
                return number(snippet);
            }

// The token is something miscellaneous.

            switch (snippet) {

// The token is a single or double quote string.

            case "\"":
                return string(snippet);

            case "'":
                if (!option.single) {
                    warn_at("use_double", line, column);
                }
                return string(snippet);

// The token is a megastring. We don't allow any kind of mega nesting.

            /*case "`":
                if (mega_mode) {
                    return stop_at("expected_a_b", line, column, "}", "`");
                }
                snippet = "";
                mega_from = from;
                mega_line = line;
                mega_mode = true;

// Parsing a mega literal is tricky. First make a ` token.

                make("`");
                from += 1;

// Then loop, building up a string, possibly from many lines, until seeing
// the end of file, a closing `, or a ${ indicting an expression within the
// string.

                (function part() {
                    var at = source_line.search(rx_mega);

// If neither ` nor ${ is seen, then the whole line joins the snippet.

                    if (at < 0) {
                        snippet += source_line + "\n";
                        return (next_line() === undefined)
                            ? stop_at("unclosed_mega", mega_line, mega_from)
                            : part();
                    }

// if either ` or ${ was found, then the preceding joins the snippet to become
// a string token.

                    snippet += source_line.slice(0, at);
                    column += at;
                    source_line = source_line.slice(at);
                    if (source_line.charAt(0) === "\\") {
                        stop_at("escape_mega", line, at);
                    }
                    make("(string)", snippet).quote = "`";
                    snippet = "";

// If ${, then make tokens that will become part of an expression until
// a } token is made.

                    if (source_line.charAt(0) === "$") {
                        column += 2;
                        make("${");
                        source_line = source_line.slice(2);
                        (function expr() {
                            var id = lex().id;
                            if (id === "{") {
                                return stop_at(
                                    "expected_a_b",
                                    line,
                                    column,
                                    "}",
                                    "{"
                                );
                            }
                            if (id !== "}") {
                                return expr();
                            }
                        }());
                        return part();
                    }
                }());
                source_line = source_line.slice(1);
                column += 1;
                mega_mode = false;
                return make("`");*/

// The token is a // comment.

            case "//":
                snippet = source_line;
                source_line = "";
                the_token = comment(snippet);
                /*if (mega_mode) {
                    warn("unexpected_comment", the_token, "`");
                }*/
                return the_token;

// The token is a /* comment.

            case "/*":
                array = [];
                if (source_line.charAt(0) === "/") {
                    warn_at("unexpected_a", line, column + i, "/");
                }
                (function next() {
                    if (source_line > "") {
                        i = source_line.search(rx_star_slash);
                        if (i >= 0) {
                            return;
                        }
                        j = source_line.search(rx_slash_star);
                        if (j >= 0) {
                            warn_at("nested_comment", line, column + j);
                        }
                    }
                    array.push(source_line);
                    source_line = next_line();
                    if (source_line === undefined) {
                        return stop_at("unclosed_comment", line, column);
                    }
                    return next();
                }());
                snippet = source_line.slice(0, i);
                j = snippet.search(rx_slash_star_or_slash);
                if (j >= 0) {
                    warn_at("nested_comment", line, column + j);
                }
                array.push(snippet);
                column += i + 2;
                source_line = source_line.slice(i + 2);
                return comment(array);

// The token is a slash.

            case "/":

// The / can be a division operator or the beginning of a regular expression
// literal. It is not possible to know which without doing a complete parse.
// We want to complete the tokenization before we begin to parse, so we will
// estimate. This estimator can fail in some cases. For example, it cannot
// know if "}" is ending a block or ending an object literal, so it can
// behave incorrectly in that case; it is not meaningful to divide an
// object, so it is likely that we can get away with it. We avoided the worst
// cases by eliminating automatic semicolon insertion.

                if (prior.identifier) {
                    if (!prior.dot) {
                        switch (prior.id) {
                        case "return":
                            return regexp();
                        case "(begin)":
                        case "case":
                        case "delete":
                        case "in":
                        case "instanceof":
                        case "new":
                        case "typeof":
                        case "void":
                        case "yield":
                            the_token = regexp();
                            return stop("unexpected_a", the_token);
                        }
                    }
                } else {
                    last = prior.id.charAt(prior.id.length - 1);
                    if ("(,=:?[".indexOf(last) >= 0) {
                        return regexp();
                    }
                    if ("!&|{};~+-*%/^<>".indexOf(last) >= 0) {
                        the_token = regexp();
                        warn("wrap_regexp", the_token);
                        return the_token;
                    }
                }
                if (source_line.charAt(0) === "/") {
                    column += 1;
                    source_line = source_line.slice(1);
                    snippet = "/=";
                    warn_at("unexpected_a", line, column, "/=");
                }
                break;
            }
            return make(snippet);
        }

        first = lex();
        json_mode = first.id === "{" || first.id === "[";

// This is the only loop in JSLint. It will turn into a recursive call to lex
// when ES6 has been finished and widely deployed and adopted.

        while (true) {
            if (lex().id === "(end)") {
                break;
            }
        }
    }

// Parsing:

// Parsing weaves the tokens into an abstract syntax tree. During that process,
// a token may be given any of these properties:

//      arity       string
//      label       identifier
//      name        identifier
//      expression  expressions
//      block       statements
//      else        statements (else, default, catch)

// Specialized tokens may have additional properties.

    function survey(name) {
        var id = name.id;

// Tally the property name. If it is a string, only tally strings that conform
// to the identifier rules.

        if (id === "(string)") {
            id = name.value;
            if (!rx_identifier.test(id)) {
                return id;
            }
        /*} else if (id === "`") {
            if (name.value.length === 1) {
                id = name.value[0].value;
                if (!rx_identifier.test(id)) {
                    return id;
                }
            }*/
        } else if (!name.identifier) {
            return stop("expected_identifier_a", name);
        }

// If we have seen this name before, increment its count.

        if (typeof property[id] === "number") {
            property[id] += 1;

// If this is the first time seeing this property name, and if there is a
// tenure list, then it must be on the list. Otherwise, it must conform to
// the rules for good property names.

        } else {
            if (tenure !== undefined) {
                if (tenure[id] !== true) {
                    warn("unregistered_property_a", name);
                }
            } else {
                if (name.identifier && rx_bad_property.test(id)) {
                    warn("bad_property_a", name);
                }
            }
            property[id] = 1;
        }
        return id;
    }

    function dispense() {

// Deliver the next token, skipping the comments.

        var cadet = tokens[token_nr];
        token_nr += 1;
        if (cadet.id === "(comment)") {
            if (json_mode) {
                warn("unexpected_a", cadet);
            }
            return dispense();
        } else {
            return cadet;
        }
    }

    function lookahead() {

// Look ahead one token without advancing.

        var old_token_nr = token_nr;
        var cadet = dispense(true);
        token_nr = old_token_nr;
        return cadet;
    }

    function advance(id, match) {

// Produce the next token.

// Attempt to give helpful names to anonymous functions.

        if (token.identifier && token.id !== "function") {
            anon = token.id;
        } else if (token.id === "(string)" && rx_identifier.test(token.value)) {
            anon = token.value;
        }

// Attempt to match next_token with an expected id.

        if (id !== undefined && next_token.id !== id) {
            return (match === undefined)
                ? stop("expected_a_b", next_token, id, artifact())
                : stop(
                    "expected_a_b_from_c_d",
                    next_token,
                    id,
                    artifact(match),
                    artifact_line(match),
                    artifact(next_token)
                );
        }

// Promote the tokens, skipping comments.

        token = next_token;
        next_token = dispense();
        if (next_token.id === "(end)") {
            token_nr -= 1;
        }
    }

// Parsing of JSON is simple:

    function json_value() {
        var negative;

        function json_object() {
            var brace = next_token;
            var object = empty();
            var properties = [];
            brace.expression = properties;
            advance("{");
            if (next_token.id !== "}") {
                (function next() {
                    var name;
                    var value;
                    if (next_token.quote !== "\"") {
                        warn("unexpected_a", next_token, next_token.quote);
                    }
                    name = next_token;
                    advance("(string)");
                    if (object[token.value] !== undefined) {
                        warn("duplicate_a", token);
                    } else if (token.value === "__proto__") {
                        warn("bad_property_a", token);
                    } else {
                        object[token.value] = token;
                    }
                    advance(":");
                    value = json_value();
                    value.label = name;
                    properties.push(value);
                    if (next_token.id === ",") {
                        advance(",");
                        return next();
                    }
                }());
            }
            advance("}", brace);
            return brace;
        }

        function json_array() {
            var bracket = next_token;
            var elements = [];
            bracket.expression = elements;
            advance("[");
            if (next_token.id !== "]") {
                (function next() {
                    elements.push(json_value());
                    if (next_token.id === ",") {
                        advance(",");
                        return next();
                    }
                }());
            }
            advance("]", bracket);
            return bracket;
        }

        switch (next_token.id) {
        case "{":
            return json_object();
        case "[":
            return json_array();
        case "true":
        case "false":
        case "null":
            advance();
            return token;
        case "(number)":
            if (!rx_JSON_number.test(next_token.value)) {
                warn("unexpected_a");
            }
            advance();
            return token;
        case "(string)":
            if (next_token.quote !== "\"") {
                warn("unexpected_a", next_token, next_token.quote);
            }
            advance();
            return token;
        case "-":
            negative = next_token;
            negative.arity = "unary";
            advance("-");
            advance("(number)");
            negative.expression = token;
            return negative;
        default:
            stop("unexpected_a");
        }
    }

// Now we parse JavaScript.

    function enroll(name, role, readonly) {

// Enroll a name into the current function context. The role can be exception,
// function, label, parameter, or variable. We look for variable redefinition
// because it causes confusion.

        var id = name.id;

// Reserved words may not be enrolled.

        if (syntax[id] !== undefined && id !== "ignore") {
            warn("reserved_a", name);
        } else {

// Has the name been enrolled in this context?

            var earlier = functionage.context[id];
            if (earlier) {
                warn(
                    "redefinition_a_b",
                    name,
                    name.id,
                    earlier.line + fudge
                );

// Has the name been enrolled in an outer context?

            } else {
                stack.forEach(function (value) {
                    var item = value.context[id];
                    if (item !== undefined) {
                        earlier = item;
                    }
                });
                if (earlier) {
                    if (id === "ignore") {
                        if (earlier.role === "variable") {
                            warn("unexpected_a", name);
                        }
                    } else {
                        if ((
                            role !== "exception"
                            || earlier.role !== "exception"
                        ) && role !== "parameter") {
                            warn(
                                "redefinition_a_b",
                                name,
                                name.id,
                                earlier.line + fudge
                            );
                        }
                    }
                }

// Enroll it.

                functionage.context[id] = name;
                name.dead = true;
                name.function = functionage;
                name.init = false;
                name.role = role;
                name.used = 0;
                name.writable = !readonly;
            }
        }
    }

    function expression(rbp, initial) {

// This is the heart of the Pratt parser. I retained Pratt's nomenclature.
// They are elements of the parsing method called Top Down Operator Precedence.

// nud     Null denotation
// led     Left denotation
// lbp     Left binding power
// rbp     Right binding power

// It processes a nud (variable, constant, prefix operator). It will then
// process leds (infix operators) until the bind powers cause it to stop. It
// returns the expression's parse tree.

        var left;
        var the_symbol;

// Statements will have already advanced, so advance now only if the token is
// not the first of a statement,

        if (!initial) {
            advance();
        }
        the_symbol = syntax[token.id];
        if (the_symbol !== undefined && the_symbol.nud !== undefined) {
            left = the_symbol.nud();
        } else if (token.identifier) {
            left = token;
            left.arity = "variable";
        } else {
            return stop("unexpected_a", token);
        }
        (function right() {
            the_symbol = syntax[next_token.id];
            if (
                the_symbol !== undefined
                && the_symbol.led !== undefined
                && rbp < the_symbol.lbp
            ) {
                advance();
                left = the_symbol.led(left);
                return right();
            }
        }());
        return left;
    }

    function condition() {

// Parse the condition part of a do, if, while.

        var the_paren = next_token;
        var the_value;
        the_paren.free = true;
        advance("(");
        the_value = expression(0);
        advance(")");
        if (the_value.wrapped === true) {
            warn("unexpected_a", the_paren);
        }
        switch (the_value.id) {
        case "?":
        case "~":
        case "&":
        case "|":
        case "^":
        case "<<":
        case ">>":
        case ">>>":
        case "+":
        case "-":
        case "*":
        case "/":
        case "%":
        case "typeof":
        case "(number)":
        case "(string)":
            warn("unexpected_a", the_value);
            break;
        }
        return the_value;
    }

    function is_weird(thing) {
        return (
            thing.id === "(regexp)"
            || thing.id === "{"
//            || thing.id === "=>"
            || thing.id === "function"
            || (thing.id === "[" && thing.arity === "unary")
        );
    }

    function are_similar(a, b) {
        if (a === b) {
            return true;
        }
        if (Array.isArray(a)) {
            return (
                Array.isArray(b)
                && a.length === b.length
                && a.every(function (value, index) {
                    return are_similar(value, b[index]);
                })
            );
        }
        if (Array.isArray(b)) {
            return false;
        }
        if (a.id === "(number)" && b.id === "(number)") {
            return a.value === b.value;
        }
        var a_string;
        var b_string;
        if (a.id === "(string)") {
            a_string = a.value;
        } /*else if (a.id === "`" && a.constant) {
            a_string = a.value[0];
        }*/
        if (b.id === "(string)") {
            b_string = b.value;
        } /*else if (b.id === "`" && b.constant) {
            b_string = b.value[0];
        }*/
        if (typeof a_string === "string") {
            return a_string === b_string;
        }
        if (is_weird(a) || is_weird(b)) {
            return false;
        }
        if (a.arity === b.arity && a.id === b.id) {
            if (a.id === ".") {
                return (
                    are_similar(a.expression, b.expression)
                    && are_similar(a.name, b.name)
                );
            }
            switch (a.arity) {
            case "unary":
                return are_similar(a.expression, b.expression);
            case "binary":
                return (
                    a.id !== "("
                    && are_similar(a.expression[0], b.expression[0])
                    && are_similar(a.expression[1], b.expression[1])
                );
            case "ternary":
                return (
                    are_similar(a.expression[0], b.expression[0])
                    && are_similar(a.expression[1], b.expression[1])
                    && are_similar(a.expression[2], b.expression[2])
                );
            case "function":
            case "regexp":
                return false;
            default:
                return true;
            }
        }
        return false;
    }

    function semicolon() {

// Try to match a semicolon.

        if (next_token.id === ";") {
            advance(";");
        } else {
            warn_at(
                "expected_a_b",
                token.line,
                token.thru,
                ";",
                artifact(next_token)
            );
        }
        anon = "anonymous";
    }

    function statement() {

// Parse a statement. Any statement may have a label, but only four statements
// have use for one. A statement can be one of the standard statements, or
// an assignment expression, or an invocation expression.

        var first;
        var the_label;
        var the_statement;
        var the_symbol;
        advance();
        if (token.identifier && next_token.id === ":") {
            the_label = token;
            if (the_label.id === "ignore") {
                warn("unexpected_a", the_label);
            }
            advance(":");
            switch (next_token.id) {
            case "do":
            case "for":
            case "switch":
            case "while":
                enroll(the_label, "label", true);
                the_label.init = true;
                the_label.dead = false;
                the_statement = statement();
                the_statement.label = the_label;
                the_statement.statement = true;
                return the_statement;
            default:
                advance();
                warn("unexpected_label_a", the_label);
            }
        }

// Parse the statement.

        first = token;
        first.statement = true;
        the_symbol = syntax[first.id];
        if (the_symbol !== undefined && the_symbol.fud !== undefined) {
            the_symbol.disrupt = false;
            the_symbol.statement = true;
            the_statement = the_symbol.fud();
        } else {

// It is an expression statement.

            the_statement = expression(0, true);
            if (the_statement.wrapped && the_statement.id !== "(") {
                warn("unexpected_a", first);
            }
            semicolon();
        }
        if (the_label !== undefined) {
            the_label.dead = true;
        }
        return the_statement;
    }

    function statements() {

// Parse a list of statements. Give a warning if an unreachable statement
// follows a disruptive statement.

        var array = [];
        (function next(disrupt) {
            var a_statement;
            switch (next_token.id) {
            case "}":
            case "case":
            case "default":
            case "else":
            case "(end)":
                break;
            default:
                a_statement = statement();
                array.push(a_statement);
                if (disrupt) {
                    warn("unreachable_a", a_statement);
                }
                return next(a_statement.disrupt);
            }
        }(false));
        return array;
    }

    function not_top_level(thing) {

// Some features should not be at the outermost level.

        if (functionage === global) {
            warn("unexpected_at_top_level_a", thing);
        }
    }

    function top_level_only(the_thing) {

// Some features must be at the most outermost level.

        if (blockage !== global) {
            warn("misplaced_a", the_thing);
        }
    }

    function block(special) {

// Parse a block, a sequence of statements wrapped in braces.
//  special "body"      The block is a function body.
//          "ignore"    No warning on an empty block.
//          "naked"     No advance.
//          undefined   An ordinary block.

        var stmts;
        var the_block;
        if (special !== "naked") {
            advance("{");
        }
        the_block = token;
        the_block.arity = "statement";
        the_block.body = special === "body";

// All top level function bodies should include the "use strict" pragma unless
// the whole file is strict or the file is a module or the function parameters
// use es6 syntax.

        if (
            special === "body"
            && stack.length === 1
            && next_token.value === "use strict"
        ) {
            the_block.strict = next_token;
            next_token.statement = true;
            advance("(string)");
            advance(";");
        }
        stmts = statements();
        the_block.block = stmts;
        if (stmts.length === 0) {
            if (!option.devel && special !== "ignore") {
                warn("empty_block", the_block);
            }
            the_block.disrupt = false;
        } else {
            the_block.disrupt = stmts[stmts.length - 1].disrupt;
        }
        advance("}");
        return the_block;
    }

    function mutation_check(the_thing) {

// The only expressions that may be assigned to are
//      e.b
//      e[b]
//      v
//      [destructure]
//      {destructure}

        if (
            the_thing.arity !== "variable"
            && the_thing.id !== "."
            && the_thing.id !== "["
            && the_thing.id !== "{"
        ) {
            warn("bad_assignment_a", the_thing);
            return false;
        }
        return true;
    }

    function left_check(left, right) {

// Warn if the left is not one of these:
//      e.b
//      e[b]
//      e()
//      identifier

        var id = left.id;
        if (
            !left.identifier
            && (
                left.arity !== "binary"
                || (id !== "." && id !== "(" && id !== "[")
            )
        ) {
            warn("unexpected_a", right);
            return false;
        }
        return true;
    }

// These functions are used to specify the grammar of our language:

    function symbol(id, bp) {

// Make a symbol if it does not already exist in the language's syntax.

        var the_symbol = syntax[id];
        if (the_symbol === undefined) {
            the_symbol = empty();
            the_symbol.id = id;
            the_symbol.lbp = bp || 0;
            syntax[id] = the_symbol;
        }
        return the_symbol;
    }

    function assignment(id) {

// Make an assignment operator. The one true assignment is different because
// its left side, when it is a variable, is not treated as an expression.
// That case is special because that is when a variable gets initialized. The
// other assignment operators can modify, but they cannot initialize.

        var the_symbol = symbol(id, 20);
        the_symbol.led = function (left) {
            var the_token = token;
            var right;
            the_token.arity = "assignment";
            right = expression(20 - 1);
            if (id === "=" && left.arity === "variable") {
                the_token.names = left;
                the_token.expression = right;
            } else {
                the_token.expression = [left, right];
            }
            switch (right.arity) {
            case "assignment":
            case "pre":
            case "post":
                warn("unexpected_a", right);
                break;
            }
            mutation_check(left);
            return the_token;
        };
        return the_symbol;
    }

    function constant(id, type, value) {

// Make a constant symbol.

        var the_symbol = symbol(id);
        the_symbol.constant = true;
        the_symbol.nud = (typeof value === "function")
            ? value
            : function () {
                token.constant = true;
                if (value !== undefined) {
                    token.value = value;
                }
                return token;
            };
        the_symbol.type = type;
        the_symbol.value = value;
        return the_symbol;
    }

    function infix(id, bp, f) {

// Make an infix operator.

        var the_symbol = symbol(id, bp);
        the_symbol.led = function (left) {
            var the_token = token;
            the_token.arity = "binary";
            if (f !== undefined) {
                return f(left);
            }
            the_token.expression = [left, expression(bp)];
            return the_token;
        };
        return the_symbol;
    }

    function infixr(id, bp) {

// Make a right associative infix operator.

        var the_symbol = symbol(id, bp);
        the_symbol.led = function (left) {
            var the_token = token;
            the_token.arity = "binary";
            the_token.expression = [left, expression(bp - 1)];
            return the_token;
        };
        return the_symbol;
    }

    function post(id) {

// Make one of the post operators.

        var the_symbol = symbol(id, 150);
        the_symbol.led = function (left) {
            token.expression = left;
            token.arity = "post";
            mutation_check(token.expression);
            return token;
        };
        return the_symbol;
    }

    function pre(id) {

// Make one of the pre operators.

        var the_symbol = symbol(id);
        the_symbol.nud = function () {
            var the_token = token;
            the_token.arity = "pre";
            the_token.expression = expression(150);
            mutation_check(the_token.expression);
            return the_token;
        };
        return the_symbol;
    }

    function prefix(id, f) {

// Make a prefix operator.

        var the_symbol = symbol(id);
        the_symbol.nud = function () {
            var the_token = token;
            the_token.arity = "unary";
            if (typeof f === "function") {
                return f();
            }
            the_token.expression = expression(150);
            return the_token;
        };
        return the_symbol;
    }

    function stmt(id, f) {

// Make a statement.

        var the_symbol = symbol(id);
        the_symbol.fud = function () {
            token.arity = "statement";
            return f();
        };
        return the_symbol;
    }

    function ternary(id1, id2) {

// Make a ternary operator.

        var the_symbol = symbol(id1, 30);
        the_symbol.led = function (left) {
            var the_token = token;
            var second = expression(20);
            advance(id2);
            token.arity = "ternary";
            the_token.arity = "ternary";
            the_token.expression = [left, second, expression(10)];
            return the_token;
        };
        return the_symbol;
    }

// Begin defining the language.

    syntax = empty();

    symbol("}");
    symbol(")");
    symbol("]");
    symbol(",");
    symbol(";");
    symbol(":");
    symbol("*/");
    symbol("await");
    symbol("case");
    symbol("catch");
    symbol("class");
    symbol("default");
    symbol("else");
    symbol("enum");
    symbol("finally");
    symbol("implements");
    symbol("interface");
    symbol("package");
    symbol("private");
    symbol("protected");
    symbol("public");
    symbol("static");
    symbol("super");
    symbol("void");
    symbol("yield");

    constant("(number)", "number");
    constant("(regexp)", "regexp");
    constant("(string)", "string");
    constant("arguments", "object", function () {
        warn("unexpected_a", token);
        return token;
    });
    constant("eval", "function", function () {
        if (!option.eval) {
            warn("unexpected_a", token);
        } else if (next_token.id !== "(") {
            warn("expected_a_before_b", next_token, "(", artifact());
        }
        return token;
    });
    constant("false", "boolean", false);
    constant("Function", "function", function () {
        if (!option.eval) {
            warn("unexpected_a", token);
        } else if (next_token.id !== "(") {
            warn("expected_a_before_b", next_token, "(", artifact());
        }
        return token;
    });
    constant("ignore", "undefined", function () {
        warn("unexpected_a", token);
        return token;
    });
    constant("Infinity", "number", Infinity);
    constant("isFinite", "function", function () {
        warn("expected_a_b", token, "Number.isFinite", "isFinite");
        return token;
    });
    /*constant("isNaN", "function", function () {
        warn("number_isNaN", token);
        return token;
    }); */
    constant("NaN", "number", NaN);
    constant("null", "null", null);
    constant("this", "object", function () {
        if (!option.this) {
            warn("unexpected_a", token);
        }
        return token;
    });
    constant("true", "boolean", true);
    constant("undefined", "undefined");

    assignment("=");
    assignment("+=");
    assignment("-=");
    assignment("*=");
    assignment("/=");
    assignment("%=");
    assignment("&=");
    assignment("|=");
    assignment("^=");
    assignment("<<=");
    assignment(">>=");
    assignment(">>>=");

    infix("||", 40);
    infix("&&", 50);
    infix("|", 70);
    infix("^", 80);
    infix("&", 90);
    infix("==", 100);
    infix("===", 100);
    infix("!=", 100);
    infix("!==", 100);
    infix("<", 110);
    infix(">", 110);
    infix("<=", 110);
    infix(">=", 110);
    infix("in", 110);
    infix("instanceof", 110);
    infix("<<", 120);
    infix(">>", 120);
    infix(">>>", 120);
    infix("+", 130);
    infix("-", 130);
    infix("*", 140);
    infix("/", 140);
    infix("%", 140);
    infixr("**", 150);
    infix("(", 160, function (left) {
        var the_paren = token;
        var the_argument;
        if (left.id !== "function") {
            left_check(left, the_paren);
        }
        if (functionage.arity === "statement" && left.identifier) {
            functionage.name.calls[left.id] = left;
        }
        the_paren.expression = [left];
        if (next_token.id !== ")") {
            (function next() {
                var ellipsis;
                if (next_token.id === "...") {
                    ellipsis = true;
                    advance("...");
                }
                the_argument = expression(10);
                if (ellipsis) {
                    the_argument.ellipsis = true;
                }
                the_paren.expression.push(the_argument);
                if (next_token.id === ",") {
                    advance(",");
                    return next();
                }
            }());
        }
        advance(")", the_paren);
        if (the_paren.expression.length === 2) {
            the_paren.free = true;
            if (the_argument.wrapped === true) {
                warn("unexpected_a", the_paren);
            }
            if (the_argument.id === "(") {
                the_argument.wrapped = true;
            }
        } else {
            the_paren.free = false;
        }
        return the_paren;
    });
    infix(".", 170, function (left) {
        var the_token = token;
        var name = next_token;
        if (
            (left.id !== "(string)" || (
                name.id !== "indexOf" && name.id !== "repeat"
            ))
            && (left.id !== "[" || (
                name.id !== "concat" && name.id !== "forEach"
            ))
            && (left.id !== "+" || name.id !== "slice")
            && (left.id !== "(regexp)" || (
                name.id !== "exec" && name.id !== "test"
            ))
        ) {
            left_check(left, the_token);
        }
        if (!name.identifier) {
            stop("expected_identifier_a");
        }
        advance();
        survey(name);

// The property name is not an expression.

        the_token.name = name;
        the_token.expression = left;
        return the_token;
    });
    infix("[", 170, function (left) {
        var the_token = token;
        var the_subscript = expression(0);
        if (the_subscript.id === "(string)" /*|| the_subscript.id === "`"*/) {
            var name = survey(the_subscript);
            if (rx_identifier.test(name)) {
                warn("subscript_a", the_subscript, name);
            }
        }
        left_check(left, the_token);
        the_token.expression = [left, the_subscript];
        advance("]");
        return the_token;
    });
    /*infix("=>", 170, function (left) {
        return stop("wrap_parameter", left);
    });*/

    /*function do_tick() {
        var the_tick = token;
        the_tick.value = [];
        the_tick.expression = [];
        if (next_token.id !== "`") {
            (function part() {
                advance("(string)");
                the_tick.value.push(token);
                if (next_token.id === "${") {
                    advance("${");
                    the_tick.expression.push(expression(0));
                    advance("}");
                    return part();
                }
            }());
        }
        advance("`");
        return the_tick;
    }

    infix("`", 160, function (left) {
        var the_tick = do_tick();
        left_check(left, the_tick);
        the_tick.expression = [left].concat(the_tick.expression);
        return the_tick;
    });*/

    post("++");
    post("--");
    pre("++");
    pre("--");

    prefix("+");
    prefix("-");
    prefix("~");
    prefix("!");
    prefix("!!");
    prefix("[", function () {
        var the_token = token;
        the_token.expression = [];
        if (next_token.id !== "]") {
            (function next() {
                var element;
                var ellipsis = false;
                if (next_token.id === "...") {
                    ellipsis = true;
                    advance("...");
                }
                element = expression(10);
                if (ellipsis) {
                    element.ellipsis = true;
                }
                the_token.expression.push(element);
                if (next_token.id === ",") {
                    advance(",");
                    return next();
                }
            }());
        }
        advance("]");
        return the_token;
    });
    prefix("/=", function () {
        stop("expected_a_b", token, "/\\=", "/=");
    });
    /*prefix("=>", function () {
        return stop("expected_a_before_b", token, "()", "=>");
    });*/
    prefix("new", function () {
        var the_new = token;
        var right = expression(160);
        if (next_token.id !== "(") {
            warn("expected_a_before_b", next_token, "()", artifact(next_token));
        }
        the_new.expression = right;
        return the_new;
    });
    prefix("typeof");
    prefix("void", function () {
        var the_void = token;
        warn("unexpected_a", the_void);
        the_void.expression = expression(0);
        return the_void;
    });

    function parameter_list() {
        var complex = false;
        var list = [];
        var optional;
        var signature = ["("];
        if (next_token.id !== ")" && next_token.id !== "(end)") {
            (function parameter() {
                var ellipsis = false;
                var param;
                if (next_token.id === "{") {
                    complex = true;
                    if (optional !== undefined) {
                        warn(
                            "required_a_optional_b",
                            next_token,
                            next_token.id,
                            optional.id
                        );
                    }
                    param = next_token;
                    param.names = [];
                    advance("{");
                    signature.push("{");
                    (function subparameter() {
                        var subparam = next_token;
                        if (!subparam.identifier) {
                            return stop("expected_identifier_a");
                        }
                        survey(subparam);
                        advance();
                        signature.push(subparam.id);
                        if (next_token.id === ":") {
                            advance(":");
                            advance();
                            token.label = subparam;
                            subparam = token;
                            if (!subparam.identifier) {
                                return stop("expected_identifier_a");
                            }
                        }
                        param.names.push(subparam);
                        if (next_token.id === ",") {
                            advance(",");
                            signature.push(", ");
                            return subparameter();
                        }
                    }());
                    list.push(param);
                    advance("}");
                    signature.push("}");
                    if (next_token.id === ",") {
                        advance(",");
                        signature.push(", ");
                        return parameter();
                    }
                } else if (next_token.id === "[") {
                    complex = true;
                    if (optional !== undefined) {
                        warn(
                            "required_a_optional_b",
                            next_token,
                            next_token.id,
                            optional.id
                        );
                    }
                    param = next_token;
                    param.names = [];
                    advance("[");
                    signature.push("[]");
                    (function subparameter() {
                        var subparam = next_token;
                        if (!subparam.identifier) {
                            return stop("expected_identifier_a");
                        }
                        advance();
                        param.names.push(subparam);
                        if (next_token.id === ",") {
                            advance(",");
                            return subparameter();
                        }
                    }());
                    list.push(param);
                    advance("]");
                    if (next_token.id === ",") {
                        advance(",");
                        signature.push(", ");
                        return parameter();
                    }
                } else {
                    if (next_token.id === "...") {
                        complex = true;
                        ellipsis = true;
                        signature.push("...");
                        advance("...");
                        if (optional !== undefined) {
                            warn(
                                "required_a_optional_b",
                                next_token,
                                next_token.id,
                                optional.id
                            );
                        }
                    }
                    if (!next_token.identifier) {
                        return stop("expected_identifier_a");
                    }
                    param = next_token;
                    list.push(param);
                    advance();
                    signature.push(param.id);
                    if (ellipsis) {
                        param.ellipsis = true;
                    } else {
                        if (next_token.id === "=") {
                            complex = true;
                            optional = param;
                            advance("=");
                            param.expression = expression(0);
                        } else {
                            if (optional !== undefined) {
                                warn(
                                    "required_a_optional_b",
                                    param,
                                    param.id,
                                    optional.id
                                );
                            }
                        }
                        if (next_token.id === ",") {
                            advance(",");
                            signature.push(", ");
                            return parameter();
                        }
                    }
                }
            }());
        }
        advance(")");
        signature.push(")");
        return [list, signature.join(""), complex];
    }

    function do_function(the_function) {
        var name;
        if (the_function === undefined) {
            the_function = token;

// A function statement must have a name that will be in the parent's scope.

            if (the_function.arity === "statement") {
                if (!next_token.identifier) {
                    return stop("expected_identifier_a", next_token);
                }
                name = next_token;
                enroll(name, "variable", true);
                the_function.name = name;
                name.init = true;
                name.calls = empty();
                advance();
            } else if (name === undefined) {

// A function expression may have an optional name.

                if (next_token.identifier) {
                    name = next_token;
                    the_function.name = name;
                    advance();
                } else {
                    the_function.name = anon;
                }
            }
        } else {
            name = the_function.name;
        }
        the_function.level = functionage.level + 1;
        /*if (mega_mode) {
            warn("unexpected_a", the_function);
        }*/

// Don't make functions in loops. It is inefficient, and it can lead to scoping
// errors.

        if (functionage.loop > 0) {
            warn("function_in_loop", the_function);
        }

// Give the function properties for storing its names and for observing the
// depth of loops and switches.

        the_function.context = empty();
        the_function.finally = 0;
        the_function.loop = 0;
        the_function.switch = 0;
        the_function.try = 0;

// Push the current function context and establish a new one.

        stack.push(functionage);
        functions.push(the_function);
        functionage = the_function;
        if (the_function.arity !== "statement" && typeof name === "object") {
            enroll(name, "function", true);
            name.dead = false;
            name.init = true;
            name.used = 1;
        }

// Parse the parameter list.

        advance("(");
        token.free = false;
        token.arity = "function";
        var pl = parameter_list();
        functionage.parameters = pl[0];
        functionage.signature = pl[1];
        functionage.complex = pl[2];
        functionage.parameters.forEach(function enroll_parameter(name) {
            if (name.identifier) {
                enroll(name, "parameter", false);
            } else {
                name.names.forEach(enroll_parameter);
            }
        });

// The function's body is a block.

        the_function.block = block("body");
        if (
            the_function.arity === "statement"
            && next_token.line === token.line
        ) {
            return stop("unexpected_a", next_token);
        }
        if (next_token.id === "." || next_token.id === "[") {
            warn("unexpected_a");
        }

// Restore the previous context.

        functionage = stack.pop();
        return the_function;
    }

    prefix("function", do_function);

    /*function fart(pl) {
        advance(";");
        if (next_token.id === ";") {
            stop("wrap_assignment", token);
        }
        advance("=>");
        var the_fart = token;
        the_fart.arity = "binary";
        the_fart.name = "=>";
        the_fart.level = functionage.level + 1;
        functions.push(the_fart);
        if (functionage.loop > 0) {
            warn("function_in_loop", the_fart);
        }

// Give the function properties storing its names and for observing the depth
// of loops and switches.

        the_fart.context = empty();
        the_fart.finally = 0;
        the_fart.loop = 0;
        the_fart.switch = 0;
        the_fart.try = 0;

// Push the current function context and establish a new one.

        stack.push(functionage);
        functionage = the_fart;
        the_fart.parameters = pl[0];
        the_fart.signature = pl[1];
        the_fart.complex = true;
        the_fart.parameters.forEach(function (name) {
            enroll(name, "parameter", true);
        });
        if (next_token.id === "{") {
            warn("expected_a_b", the_fart, "function", "=>");
            the_fart.block = block("body");
        } else {
            the_fart.expression = expression(0);
        }
        functionage = stack.pop();
        return the_fart;
    }*/

    prefix("(", function () {
        var the_paren = token;
        var the_value;
        var cadet = lookahead().id;

// We can distinguish between a parameter list for => and a wrapped expression
// with one token of lookahead.

        /*if (
            next_token.id === ")"
            || next_token.id === "..."
            || (next_token.identifier && (cadet === "," || cadet === "="))
        ) {
            the_paren.free = false;
            return fart(parameter_list());
        }*/
        the_paren.free = true;
        the_value = expression(0);
        if (the_value.wrapped === true) {
            warn("unexpected_a", the_paren);
        }
        the_value.wrapped = true;
        advance(")", the_paren);
        /*if (next_token.id === "=>") {
            if (the_value.arity !== "variable") {
                if (the_value.id === "{" || the_value.id === "[") {
                    warn("expected_a_before_b", the_paren, "function", "(");
                    return stop("expected_a_b", next_token, "{", "=>");
                }
                return stop("expected_identifier_a", the_value);
            }
            the_paren.expression = [the_value];
            return fart([the_paren.expression, "(" + the_value.id + ")"]);
        }*/
        return the_value;
    });
    //prefix("`", do_tick);
    prefix("{", function () {
        var the_brace = token;
        var seen = empty();
        the_brace.expression = [];
        if (next_token.id !== "}") {
            (function member() {
                var extra;
                var full;
                var id;
                var name = next_token;
                var value;
                advance();
                if (
                    (name.id === "get" || name.id === "set")
                    && next_token.identifier
                ) {
                    if (!option.getset) {
                        warn("unexpected_a", name);
                    }
                    extra = name.id;
                    full = extra + " " + next_token.id;
                    name = next_token;
                    advance();
                    id = survey(name);
                    if (seen[full] === true || seen[id] === true) {
                        warn("duplicate_a", name);
                    }
                    seen[id] = false;
                    seen[full] = true;
                } else {
                    id = survey(name);
                    if (typeof seen[id] === "boolean") {
                        warn("duplicate_a", name);
                    }
                    seen[id] = true;
                }
                if (name.identifier) {
                    switch (next_token.id) {
                    case "}":
                    case ",":
                        if (typeof extra === "string") {
                            advance("(");
                        }
                        value = expression(Infinity, true);
                        break;
                    case "(":
                        value = do_function({
                            arity: "unary",
                            from: name.from,
                            id: "function",
                            line: name.line,
                            name: (typeof extra === "string")
                                ? extra
                                : id,
                            thru: name.from
                        });
                        break;
                    default:
                        if (typeof extra === "string") {
                            advance("(");
                        }
                        advance(":");
                        value = expression(0);
                    }
                    value.label = name;
                    if (typeof extra === "string") {
                        value.extra = extra;
                    }
                    the_brace.expression.push(value);
                } else {
                    advance(":");
                    value = expression(0);
                    value.label = name;
                    the_brace.expression.push(value);
                }
                if (next_token.id === ",") {
                    advance(",");
                    return member();
                }
            }());
        }
        advance("}");
        return the_brace;
    });

    stmt(";", function () {
        warn("unexpected_a", token);
        return token;
    });
    stmt("{", function () {
        warn("naked_block", token);
        return block("naked");
    });
    stmt("break", function () {
        var the_break = token;
        var the_label;
        if (
            (functionage.loop < 1 && functionage.switch < 1)
            || functionage.finally > 0
        ) {
            warn("unexpected_a", the_break);
        }
        the_break.disrupt = true;
        if (next_token.identifier && token.line === next_token.line) {
            the_label = functionage.context[next_token.id];
            if (
                the_label === undefined
                || the_label.role !== "label"
                || the_label.dead
            ) {
                warn((the_label !== undefined && the_label.dead)
                    ? "out_of_scope_a"
                    : "not_label_a");
            } else {
                the_label.used += 1;
            }
            the_break.label = next_token;
            advance();
        }
        advance(";");
        return the_break;
    });

    function do_var() {
        var the_statement = token;
        var is_const = the_statement.id === "const";
        the_statement.names = [];

// A program may use var or let, but not both.

        if (!is_const) {
            if (var_mode === undefined) {
                var_mode = the_statement.id;
            } else if (the_statement.id !== var_mode) {
                warn(
                    "expected_a_b",
                    the_statement,
                    var_mode,
                    the_statement.id
                );
            }
        }

// We don't expect to see variables created in switch statements.

        if (functionage.switch > 0) {
            warn("var_switch", the_statement);
        }
        if (functionage.loop > 0 && the_statement.id === "var") {
            warn("var_loop", the_statement);
        }
        (function next() {
            if (next_token.id === "{" && the_statement.id !== "var") {
                var the_brace = next_token;
                the_brace.names = [];
                advance("{");
                (function pair() {
                    if (!next_token.identifier) {
                        return stop("expected_identifier_a", next_token);
                    }
                    var name = next_token;
                    survey(name);
                    advance();
                    if (next_token.id === ":") {
                        advance(":");
                        if (!next_token.identifier) {
                            return stop("expected_identifier_a", next_token);
                        }
                        next_token.label = name;
                        the_brace.names.push(next_token);
                        enroll(next_token, "variable", is_const);
                        advance();
                    } else {
                        the_brace.names.push(name);
                        enroll(name, "variable", is_const);
                    }
                    if (next_token.id === ",") {
                        advance(",");
                        return pair();
                    }
                }());
                advance("}");
                advance("=");
                the_brace.expression = expression(0);
                the_statement.names.push(the_brace);
            } else if (next_token.id === "[" && the_statement.id !== "var") {
                var the_bracket = next_token;
                the_bracket.names = [];
                advance("[");
                (function element() {
                    var ellipsis;
                    if (next_token.id === "...") {
                        ellipsis = true;
                        advance("...");
                    }
                    if (!next_token.identifier) {
                        return stop("expected_identifier_a", next_token);
                    }
                    var name = next_token;
                    advance();
                    the_bracket.names.push(name);
                    enroll(name, "variable", the_statement.id === "const");
                    if (ellipsis) {
                        name.ellipsis = true;
                    } else if (next_token.id === ",") {
                        advance(",");
                        return element();
                    }
                }());
                advance("]");
                advance("=");
                the_bracket.expression = expression(0);
                the_statement.names.push(the_bracket);
            } else if (next_token.identifier) {
                var name = next_token;
                advance();
                if (name.id === "ignore") {
                    warn("unexpected_a", name);
                }
                enroll(name, "variable", is_const);
                if (next_token.id === "=" || is_const) {
                    advance("=");
                    name.expression = expression(0);
                    name.init = true;
                }
                the_statement.names.push(name);
            } else {
                return stop("expected_identifier_a", next_token);
            }
            if (next_token.id === ",") {
                if (!option.multivar) {
                    warn("expected_a_b", next_token, ";", ",");
                }
                advance(",");
                return next();
            }
        }());
        the_statement.open = (
            the_statement.names.length > 1
            && the_statement.line !== the_statement.names[1].line
        );
        semicolon();
        return the_statement;
    }

    stmt("const", do_var);
    stmt("continue", function () {
        var the_continue = token;
        if (functionage.loop < 1 || functionage.finally > 0) {
            warn("unexpected_a", the_continue);
        }
        not_top_level(the_continue);
        the_continue.disrupt = true;
        warn("unexpected_a", the_continue);
        advance(";");
        return the_continue;
    });
    stmt("debugger", function () {
        var the_debug = token;
        if (!option.devel) {
            warn("unexpected_a", the_debug);
        }
        semicolon();
        return the_debug;
    });
    stmt("delete", function () {
        var the_token = token;
        var the_value = expression(0);
        if (
            (the_value.id !== "." && the_value.id !== "[")
            || the_value.arity !== "binary"
        ) {
            stop("expected_a_b", the_value, ".", artifact(the_value));
        }
        the_token.expression = the_value;
        semicolon();
        return the_token;
    });
    stmt("do", function () {
        var the_do = token;
        not_top_level(the_do);
        functionage.loop += 1;
        the_do.block = block();
        advance("while");
        the_do.expression = condition();
        semicolon();
        if (the_do.block.disrupt === true) {
            warn("weird_loop", the_do);
        }
        functionage.loop -= 1;
        return the_do;
    });
    stmt("export", function () {
        var the_export = token;
        var the_id;
        var the_name;
        var the_thing;

        function export_id() {
            if (!next_token.identifier) {
                stop("expected_identifier_a");
            }
            the_id = next_token.id;
            the_name = global.context[the_id];
            if (the_name === undefined) {
                warn("unexpected_a");
            } else {
                the_name.used += 1;
                if (exports[the_id] !== undefined) {
                    warn("duplicate_a");
                }
                exports[the_id] = the_name;
            }
            advance();
            the_export.expression.push(the_thing);
        }

        the_export.expression = [];
        if (next_token.id === "default") {
            if (exports.default !== undefined) {
                warn("duplicate_a");
            }
            advance("default");
            the_thing = expression(0);
            semicolon();
            exports.default = the_thing;
            the_export.expression.push(the_thing);
        } else {
            switch (next_token.id) {
            case "function":
                the_thing = statement();
                the_name = the_thing.name;
                the_id = the_name.id;
                the_name.used += 1;
                if (exports[the_id] !== undefined) {
                    warn("duplicate_a", the_name);
                }
                exports[the_id] = the_thing;
                the_export.expression.push(the_thing);
                the_thing.statement = false;
                the_thing.arity = "unary";
                break;
            case "var":
            case "let":
            case "const":
                warn("unexpected_a");
                break;
            case "{":
                advance("{");
                (function loop() {
                    export_id();
                    if (next_token.id === ",") {
                        advance(",");
                        return loop();
                    }
                }());
                advance("}");
                semicolon();
                break;
            default:
                export_id();
                if (the_name.writable !== true) {
                    warn("unexpected_a", token);
                }
                semicolon();
            }
        }
        module_mode = true;
        return the_export;
    });
    stmt("for", function () {
        var first;
        var the_for = token;
        if (!option.for) {
            warn("unexpected_a", the_for);
        }
        not_top_level(the_for);
        functionage.loop += 1;
        advance("(");
        token.free = true;
        if (next_token.id === ";") {
            return stop("expected_a_b", the_for, "while (", "for (;");
        }
        switch (next_token.id) {
        case "var":
        case "let":
        case "const":
            return stop("unexpected_a");
        }
        first = expression(0);
        if (first.id === "in") {
            if (first.expression[0].arity !== "variable") {
                warn("bad_assignment_a", first.expression[0]);
            }
            the_for.name = first.expression[0];
            the_for.expression = first.expression[1];
            warn("expected_a_b", the_for, "Object.keys", "for in");
        } else {
            the_for.initial = first;
            advance(";");
            the_for.expression = expression(0);
            advance(";");
            the_for.inc = expression(0);
            if (the_for.inc.id === "++") {
                warn("expected_a_b", the_for.inc, "+= 1", "++");
            }
        }
        advance(")");
        the_for.block = block();
        if (the_for.block.disrupt === true) {
            warn("weird_loop", the_for);
        }
        functionage.loop -= 1;
        return the_for;
    });
    stmt("function", do_function);
    stmt("if", function () {
        var the_else;
        var the_if = token;
        the_if.expression = condition();
        the_if.block = block();
        if (next_token.id === "else") {
            advance("else");
            the_else = token;
            the_if.else = (next_token.id === "if")
                ? statement()
                : block();
            if (the_if.block.disrupt === true) {
                if (the_if.else.disrupt === true) {
                    the_if.disrupt = true;
                } else {
                    warn("unexpected_a", the_else);
                }
            }
        }
        return the_if;
    });
    stmt("import", function () {
        var the_import = token;
        var name;
        if (typeof module_mode === "object") {
            warn("unexpected_directive_a", module_mode, module_mode.directive);
        }
        module_mode = true;
        if (next_token.identifier) {
            name = next_token;
            advance();
            if (name.id === "ignore") {
                warn("unexpected_a", name);
            }
            enroll(name, "variable", true);
            the_import.name = name;
        } else {
            var names = [];
            advance("{");
            if (next_token.id !== "}") {
                while (true) {
                    if (!next_token.identifier) {
                        stop("expected_identifier_a");
                    }
                    name = next_token;
                    advance();
                    if (name.id === "ignore") {
                        warn("unexpected_a", name);
                    }
                    enroll(name, "variable", true);
                    names.push(name);
                    if (next_token.id !== ",") {
                        break;
                    }
                    advance(",");
                }
            }
            advance("}");
            the_import.name = names;
        }
        advance("from");
        advance("(string)");
        the_import.import = token;
        if (!rx_module.test(token.value)) {
            warn("bad_module_name_a", token);
        }
        froms.push(token.value);
        semicolon();
        return the_import;
    });
    stmt("let", do_var);
    stmt("return", function () {
        var the_return = token;
        not_top_level(the_return);
        if (functionage.finally > 0) {
            warn("unexpected_a", the_return);
        }
        the_return.disrupt = true;
        if (next_token.id !== ";" && the_return.line === next_token.line) {
            the_return.expression = expression(10);
        }
        advance(";");
        return the_return;
    });
    stmt("switch", function () {
        var dups = [];
        var last;
        var stmts;
        var the_cases = [];
        var the_disrupt = true;
        var the_switch = token;
        not_top_level(the_switch);
        if (functionage.finally > 0) {
            warn("unexpected_a", the_switch);
        }
        functionage.switch += 1;
        advance("(");
        token.free = true;
        the_switch.expression = expression(0);
        the_switch.block = the_cases;
        advance(")");
        advance("{");
        (function major() {
            var the_case = next_token;
            the_case.arity = "statement";
            the_case.expression = [];
            (function minor() {
                advance("case");
                token.switch = true;
                var exp = expression(0);
                if (dups.some(function (thing) {
                    return are_similar(thing, exp);
                })) {
                    warn("unexpected_a", exp);
                }
                dups.push(exp);
                the_case.expression.push(exp);
                advance(":");
                if (next_token.id === "case") {
                    return minor();
                }
            }());
            stmts = statements();
            if (stmts.length < 1) {
                warn("expected_statements_a");
                return;
            }
            the_case.block = stmts;
            the_cases.push(the_case);
            last = stmts[stmts.length - 1];
            if (last.disrupt) {
                if (last.id === "break" && last.label === undefined) {
                    the_disrupt = false;
                }
            } else {
                warn(
                    "expected_a_before_b",
                    next_token,
                    "break;",
                    artifact(next_token)
                );
            }
            if (next_token.id === "case") {
                return major();
            }
        }());
        dups = undefined;
        if (next_token.id === "default") {
            var the_default = next_token;
            advance("default");
            token.switch = true;
            advance(":");
            the_switch.else = statements();
            if (the_switch.else.length < 1) {
                warn("unexpected_a", the_default);
                the_disrupt = false;
            } else {
                var the_last = the_switch.else[the_switch.else.length - 1];
                if (the_last.id === "break" && the_last.label === undefined) {
                    warn("unexpected_a", the_last);
                    the_last.disrupt = false;
                }
                the_disrupt = the_disrupt && the_last.disrupt;
            }
        } else {
            the_disrupt = false;
        }
        advance("}", the_switch);
        functionage.switch -= 1;
        the_switch.disrupt = the_disrupt;
        return the_switch;
    });
    stmt("throw", function () {
        var the_throw = token;
        the_throw.disrupt = true;
        the_throw.expression = expression(10);
        semicolon();
        return the_throw;
    });
    stmt("try", function () {
        var the_catch;
        var the_disrupt;
        var the_try = token;
        if (functionage.try > 0) {
            warn("unexpected_a", the_try);
        }
        functionage.try += 1;
        the_try.block = block();
        the_disrupt = the_try.block.disrupt;
        if (next_token.id === "catch") {
            var ignored = "ignore";
            the_catch = next_token;
            the_try.catch = the_catch;
            advance("catch");
            advance("(");
            if (!next_token.identifier) {
                return stop("expected_identifier_a", next_token);
            }
            if (next_token.id !== "ignore") {
                ignored = undefined;
                the_catch.name = next_token;
                enroll(next_token, "exception", true);
            }
            advance();
            advance(")");
            the_catch.block = block(ignored);
            if (the_catch.block.disrupt !== true) {
                the_disrupt = false;
            }
        } else {
            warn(
                "expected_a_before_b",
                next_token,
                "catch",
                artifact(next_token)
            );

        }
        if (next_token.id === "finally") {
            functionage.finally += 1;
            advance("finally");
            the_try.else = block();
            the_disrupt = the_try.else.disrupt;
            functionage.finally -= 1;
        }
        the_try.disrupt = the_disrupt;
        functionage.try -= 1;
        return the_try;
    });
    stmt("var", do_var);
    stmt("while", function () {
        var the_while = token;
        not_top_level(the_while);
        functionage.loop += 1;
        the_while.expression = condition();
        the_while.block = block();
        if (the_while.block.disrupt === true) {
            warn("weird_loop", the_while);
        }
        functionage.loop -= 1;
        return the_while;
    });
    stmt("with", function () {
        stop("unexpected_a", token);
    });

    ternary("?", ":");

// Ambulation of the parse tree.

    function action(when) {

// Produce a function that will register task functions that will be called as
// the tree is traversed.

        return function (arity, id, task) {
            var a_set = when[arity];
            var i_set;

// The id parameter is optional. If excluded, the task will be applied to all
// ids.

            if (typeof id !== "string") {
                task = id;
                id = "(all)";
            }

// If this arity has no registrations yet, then create a set object to hold
// them.

            if (a_set === undefined) {
                a_set = empty();
                when[arity] = a_set;
            }

// If this id has no registrations yet, then create a set array to hold them.

            i_set = a_set[id];
            if (i_set === undefined) {
                i_set = [];
                a_set[id] = i_set;
            }

// Register the task with the arity and the id.

            i_set.push(task);
        };
    }

    function amble(when) {

// Produce a function that will act on the tasks registered by an action
// function while walking the tree.

        return function (the_token) {

// Given a task set that was built by an action function, run all of the
// relevant tasks on the token.

            var a_set = when[the_token.arity];
            var i_set;

// If there are tasks associated with the token's arity...

            if (a_set !== undefined) {

// If there are tasks associated with the token's id...

                i_set = a_set[the_token.id];
                if (i_set !== undefined) {
                    i_set.forEach(function (task) {
                        return task(the_token);
                    });
                }

// If there are tasks for all ids.

                i_set = a_set["(all)"];
                if (i_set !== undefined) {
                    i_set.forEach(function (task) {
                        return task(the_token);
                    });
                }
            }
        };
    }

    var posts = empty();
    var pres = empty();
    var preaction = action(pres);
    var postaction = action(posts);
    var preamble = amble(pres);
    var postamble = amble(posts);

    function walk_expression(thing) {
        if (thing) {
            if (Array.isArray(thing)) {
                thing.forEach(walk_expression);
            } else {
                preamble(thing);
                walk_expression(thing.expression);
                if (thing.id === "function") {
                    walk_statement(thing.block);
                }
                switch (thing.arity) {
                case "post":
                case "pre":
                    warn("unexpected_a", thing);
                    break;
                case "statement":
                case "assignment":
                    warn("unexpected_statement_a", thing);
                    break;
                }
                postamble(thing);
            }
        }
    }

    function walk_statement(thing) {
        if (thing) {
            if (Array.isArray(thing)) {
                thing.forEach(walk_statement);
            } else {
                preamble(thing);
                walk_expression(thing.expression);
                switch (thing.arity) {
                case "statement":
                case "assignment":
                    break;
                case "binary":
                    if (thing.id !== "(") {
                        warn("unexpected_expression_a", thing);
                    }
                    break;
                default:
                    warn((
                        thing.id === "(string)"
                        && thing.value === "use strict"
                    )
                        ? "unexpected_a"
                        : "unexpected_expression_a", thing);
                }
                walk_statement(thing.block);
                walk_statement(thing.else);
                postamble(thing);
            }
        }
    }

    function lookup(thing) {
        if (thing.arity === "variable") {

// Look up the variable in the current context.

            var the_variable = functionage.context[thing.id];

// If it isn't local, search all the other contexts. If there are name
// collisions, take the most recent.

            if (the_variable === undefined) {
                stack.forEach(function (outer) {
                    var a_variable = outer.context[thing.id];
                    if (
                        a_variable !== undefined
                        && a_variable.role !== "label"
                    ) {
                        the_variable = a_variable;
                    }
                });

// If it isn't in any of those either, perhaps it is a predefined global.
// If so, add it to the global context.

                if (the_variable === undefined) {
                    if (declared_globals[thing.id] === undefined) {
                        warn("undeclared_a", thing);
                        return;
                    }
                    the_variable = {
                        dead: false,
                        function: global,
                        id: thing.id,
                        init: true,
                        role: "variable",
                        used: 0,
                        writable: false
                    };
                    global.context[thing.id] = the_variable;
                }
                the_variable.closure = true;
                functionage.context[thing.id] = the_variable;
            } else if (the_variable.role === "label") {
                warn("label_a", thing);
            }
            if (the_variable.dead) {
                warn("out_of_scope_a", thing);
            }
            return the_variable;
        }
    }

    function subactivate(name) {
        name.init = true;
        name.dead = false;
        blockage.live.push(name);
    }

    function preaction_function(thing) {
        if (thing.arity === "statement" && blockage.body !== true) {
            warn("unexpected_a", thing);
        }
        if (thing.level === 1) {
            if (
                module_mode === true
                || global.strict !== undefined
                || thing.complex
            ) {
                //if (thing.id !== "=>" && thing.block.strict !== undefined) {
                    warn("unexpected_a", thing.block.strict);
                //}
            } else {
                if (thing.block.strict === undefined) {
                    warn("use_strict", thing);
                }
            }
        }
        stack.push(functionage);
        block_stack.push(blockage);
        functionage = thing;
        blockage = thing;
        thing.live = [];
        if (typeof thing.name === "object") {
            thing.name.dead = false;
            thing.name.init = true;
        }
        switch (thing.extra) {
        case "get":
            if (thing.parameters.length !== 0) {
                warn("bad_get", thing);
            }
            break;
        case "set":
            if (thing.parameters.length !== 1) {
                warn("bad_set", thing);
            }
            break;
        }
        thing.parameters.forEach(function (name) {
            walk_expression(name.expression);
            if (name.id === "{" || name.id === "[") {
                name.names.forEach(subactivate);
            } else {
                name.dead = false;
                name.init = true;
            }
        });
    }

    function bitwise_check(thing) {
        if (!option.bitwise && bitwiseop[thing.id] === true) {
            warn("unexpected_a", thing);
        }
        if (
            thing.id !== "("
            && thing.id !== "&&"
            && thing.id !== "||"
            && thing.id !== "="
            && Array.isArray(thing.expression)
            && thing.expression.length === 2
            && (
                relationop[thing.expression[0].id] === true
                || relationop[thing.expression[1].id] === true
            )
        ) {
            warn("unexpected_a", thing);
        }
    }

    function pop_block() {
        blockage.live.forEach(function (name) {
            name.dead = true;
        });
        delete blockage.live;
        blockage = block_stack.pop();
    }

    function activate(name) {
        if (name.expression !== undefined) {
            walk_expression(name.expression);
            if (name.id === "{" || name.id === "[") {
                name.names.forEach(subactivate);
            } else {
                name.init = true;
            }
        }
        name.dead = false;
        blockage.live.push(name);
    }

    function action_var(thing) {
        thing.names.forEach(activate);
    }

    preaction("assignment", bitwise_check);
    preaction("binary", bitwise_check);
    preaction("binary", function (thing) {
        if (relationop[thing.id] === true) {
            var left = thing.expression[0];
            var right = thing.expression[1];
            /*if (left.id === "NaN" || right.id === "NaN") {
                warn("number_isNaN", thing);
            } else */if (left.id === "typeof") {
                if (right.id !== "(string)") {
                    if (right.id !== "typeof") {
                        warn("expected_string_a", right);
                    }
                } else {
                    var value = right.value;
                    if (value === "null" || value === "undefined") {
                        warn("unexpected_typeof_a", right, value);
                    } else if (
                        value !== "boolean"
                        && value !== "function"
                        && value !== "number"
                        && value !== "object"
                        && value !== "string"
                    ) {
                        warn("expected_type_string_a", right, value);
                    }
                }
            }
        }
    });
    preaction("binary", "==", function (thing) {
        warn("expected_a_b", thing, "===", "==");
    });
    preaction("binary", "!=", function (thing) {
        warn("expected_a_b", thing, "!==", "!=");
    });
    //preaction("binary", "=>", preaction_function);
    preaction("binary", "||", function (thing) {
        thing.expression.forEach(function (thang) {
            if (thang.id === "&&" && !thang.wrapped) {
                warn("and", thang);
            }
        });
    });
    preaction("binary", "(", function (thing) {
        var left = thing.expression[0];
        if (
            left.identifier
            && functionage.context[left.id] === undefined
            && typeof functionage.name === "object"
        ) {
            var parent = functionage.name.function;
            if (parent) {
                var left_variable = parent.context[left.id];
                if (
                    left_variable !== undefined
                    && left_variable.dead
                    && left_variable.function === parent
                    && left_variable.calls !== undefined
                    && left_variable.calls[functionage.name.id] !== undefined
                ) {
                    left_variable.dead = false;
                }
            }
        }
    });
    preaction("binary", "in", function (thing) {
        warn("infix_in", thing);
    });
    preaction("binary", "instanceof", function (thing) {
        warn("unexpected_a", thing);
    });
    preaction("binary", ".", function (thing) {
        if (thing.expression.new) {
            thing.new = true;
        }
    });
    preaction("statement", "{", function (thing) {
        block_stack.push(blockage);
        blockage = thing;
        thing.live = [];
    });
    preaction("statement", "for", function (thing) {
        if (thing.name !== undefined) {
            var the_variable = lookup(thing.name);
            if (the_variable !== undefined) {
                the_variable.init = true;
                if (!the_variable.writable) {
                    warn("bad_assignment_a", thing.name);
                }
            }
        }
        walk_statement(thing.initial);
    });
    preaction("statement", "function", preaction_function);
    preaction("unary", "~", bitwise_check);
    preaction("unary", "function", preaction_function);
    preaction("variable", function (thing) {
        var the_variable = lookup(thing);
        if (the_variable !== undefined) {
            thing.variable = the_variable;
            the_variable.used += 1;
        }
    });

    function init_variable(name) {
        var the_variable = lookup(name);
        if (the_variable !== undefined) {
            if (the_variable.writable) {
                the_variable.init = true;
                return;
            }
        }
        warn("bad_assignment_a", name);
    }

    postaction("assignment", function (thing) {

// Assignment using = sets the init property of a variable. No other assignment
// operator can do this. A = token keeps that variable (or array of variables
// in case of destructuring) in its name property.

        var lvalue = thing.expression[0];
        if (thing.id === "=") {
            if (thing.names !== undefined) {
                if (Array.isArray(thing.names)) {
                    thing.names.forEach(init_variable);
                } else {
                    init_variable(thing.names);
                }
            } else {
                if (
                    lvalue.id === "."
                    && thing.expression[1].id === "undefined"
                ) {
                    warn(
                        "expected_a_b",
                        lvalue.expression,
                        "delete",
                        "undefined"
                    );
                }
            }
        } else {
            if (lvalue.arity === "variable") {
                if (!lvalue.variable || lvalue.variable.writable !== true) {
                    warn("bad_assignment_a", lvalue);
                }
            }
            var right = syntax[thing.expression[1].id];
            if (
                right !== undefined
                && (
                    right.id === "function"
                    //|| right.id === "=>"
                    || (
                        right.constant
                        && right.id !== "(number)"
                        && (right.id !== "(string)" || thing.id !== "+=")
                    )
                )
            ) {
                warn("unexpected_a", thing.expression[1]);
            }
        }
    });

    function postaction_function(thing) {
        delete functionage.finally;
        delete functionage.loop;
        delete functionage.switch;
        delete functionage.try;
        functionage = stack.pop();
        if (thing.wrapped) {
            warn("unexpected_parens", thing);
        }
        return pop_block();
    }

    postaction("binary", function (thing) {
        var right;
        if (relationop[thing.id]) {
            if (
                is_weird(thing.expression[0])
                || is_weird(thing.expression[1])
                || are_similar(thing.expression[0], thing.expression[1])
                || (
                    thing.expression[0].constant === true
                    && thing.expression[1].constant === true
                )
            ) {
                warn("weird_relation_a", thing);
            }
        }
        switch (thing.id) {
        //case "=>":
        case "(":
        case "[":
            break;
        case ".":
            if (thing.expression.id === "RegExp") {
                warn("weird_expression_a", thing);
            }
            break;
        default:
            right = thing.expression[1];
            if (
                (thing.id === "+" || thing.id === "-")
                && right.id === thing.id
                && right.arity === "unary"
                && !right.wrapped
            ) {
                warn("wrap_unary", right);
            }
            if (
                thing.expression[0].constant === true
                && right.constant === true
            ) {
                thing.constant = true;
            }
        }
    });
    postaction("binary", "&&", function (thing) {
        if (
            is_weird(thing.expression[0])
            || are_similar(thing.expression[0], thing.expression[1])
            || thing.expression[0].constant === true
            || thing.expression[1].constant === true
        ) {
            warn("weird_condition_a", thing);
        }
    });
    postaction("binary", "||", function (thing) {
        if (
            is_weird(thing.expression[0])
            || are_similar(thing.expression[0], thing.expression[1])
            || thing.expression[0].constant === true
        ) {
            warn("weird_condition_a", thing);
        }
    });
    //postaction("binary", "=>", postaction_function);
    postaction("binary", "(", function (thing) {
        var left = thing.expression[0];
        var the_new;
        var arg;
        if (left.id === "new") {
            the_new = left;
            left = left.expression;
        }
        if (left.id === "function") {
            if (!thing.wrapped) {
                warn("wrap_immediate", thing);
            }
        } else if (left.identifier) {
            if (the_new !== undefined) {
                if (
                    left.id.charAt(0) > "Z"
                    || left.id === "Boolean"
                    || left.id === "Number"
                    || left.id === "String"
                    || left.id === "Symbol"
                ) {
                    warn("unexpected_a", the_new);
                } else if (left.id === "Function") {
                    if (!option.eval) {
                        warn("unexpected_a", left, "new Function");
                    }
                } else if (left.id === "Array") {
                    arg = thing.expression;
                    if (arg.length !== 2 || (
                        (
                            arg[1].id !== "(number)"
                            || +arg[1].value !== (arg[1].value | 0)
                        )
                        && arg[1].arity !== "binary"
                    )) {
                        warn("expected_a_b", left, "[]", "new Array");
                    }
                } else if (left.id === "Object") {
                    warn(
                        "expected_a_b",
                        left,
                        "Object.create(null)",
                        "new Object"
                    );
                }
            } else {
                if (
                    left.id.charAt(0) >= "A"
                    && left.id.charAt(0) <= "Z"
                    && left.id !== "Boolean"
                    && left.id !== "Number"
                    && left.id !== "String"
                    && left.id !== "Symbol"
                ) {
                    warn(
                        "expected_a_before_b",
                        left,
                        "new",
                        artifact(left)
                    );
                }
            }
        } else if (left.id === ".") {
            var cack = the_new !== undefined;
            if (left.expression.id === "Date" && left.name.id === "UTC") {
                cack = !cack;
            }
            if (rx_cap.test(left.name.id) !== cack) {
                if (the_new !== undefined) {
                    warn("unexpected_a", the_new);
                } else {
                    warn(
                        "expected_a_before_b",
                        left.expression,
                        "new",
                        left.name.id
                    );
                }
            }
            if (left.name.id === "getTime") {
                var paren = left.expression;
                if (paren.id === "(") {
                    var array = paren.expression;
                    if (array.length === 1) {
                        var new_date = array[0];
                        if (
                            new_date.id === "new"
                            && new_date.expression.id === "Date"
                        ) {
                            warn(
                                "expected_a_b",
                                new_date,
                                "Date.now()",
                                "new Date().getTime()"
                            );
                        }
                    }
                }
            }
        }
    });
    postaction("binary", "[", function (thing) {
        if (thing.expression[0].id === "RegExp") {
            warn("weird_expression_a", thing);
        }
        if (is_weird(thing.expression[1])) {
            warn("weird_expression_a", thing.expression[1]);
        }
    });
    postaction("statement", "{", pop_block);
    postaction("statement", "const", action_var);
    postaction("statement", "export", top_level_only);
    postaction("statement", "for", function (thing) {
        walk_statement(thing.inc);
    });
    postaction("statement", "function", postaction_function);
    postaction("statement", "import", function (the_thing) {
        var name = the_thing.name;
        if (Array.isArray(name)) {
            name.forEach(function (name) {
                name.dead = false;
                name.init = true;
                blockage.live.push(name);
            });
        } else {
            name.dead = false;
            name.init = true;
            blockage.live.push(name);
        }
        return top_level_only(the_thing);
    });
    postaction("statement", "let", action_var);
    postaction("statement", "try", function (thing) {
        if (thing.catch !== undefined) {
            var the_name = thing.catch.name;
            if (the_name !== undefined) {
                var the_variable = functionage.context[the_name.id];
                the_variable.dead = false;
                the_variable.init = true;
            }
            walk_statement(thing.catch.block);
        }
    });
    postaction("statement", "var", action_var);
    postaction("ternary", function (thing) {
        if (
            is_weird(thing.expression[0])
            || thing.expression[0].constant === true
            || are_similar(thing.expression[1], thing.expression[2])
        ) {
            warn("unexpected_a", thing);
        } else if (are_similar(thing.expression[0], thing.expression[1])) {
            warn("expected_a_b", thing, "||", "?");
        } else if (are_similar(thing.expression[0], thing.expression[2])) {
            warn("expected_a_b", thing, "&&", "?");
        } else if (
            thing.expression[1].id === "true"
            && thing.expression[2].id === "false"
        ) {
            warn("expected_a_b", thing, "!!", "?");
        } else if (
            thing.expression[1].id === "false"
            && thing.expression[2].id === "true"
        ) {
            warn("expected_a_b", thing, "!", "?");
        } else if (
            thing.expression[0].wrapped !== true
            && (
                thing.expression[0].id === "||"
                || thing.expression[0].id === "&&"
            )
        ) {
            warn("wrap_condition", thing.expression[0]);
        }
    });
    postaction("unary", function (thing) {
        switch (thing.id) {
        case "[":
        case "{":
        case "function":
        case "new":
            break;
        /*case "`":
            if (thing.expression.every(function (thing) {
                return thing.constant;
            })) {
                thing.constant = true;
            }
            break;*/
        case "!":
            if (thing.expression.constant === true) {
                warn("unexpected_a", thing);
            }
            break;
        default:
            if (thing.expression.constant === true) {
                thing.constant = true;
            }
        }
    });
    postaction("unary", "function", postaction_function);
    postaction("unary", "+", function (thing) {
        var right = thing.expression;
        if (right.id === "(" && right.expression[0].id === "new") {
            warn("unexpected_a_before_b", thing, "+", "new");
        } else if (
            right.constant
            || right.id === "{"
            || (right.id === "[" && right.arity !== "binary")
        ) {
            warn("unexpected_a", thing, "+");
        }
    });

    function delve(the_function) {
        Object.keys(the_function.context).forEach(function (id) {
            if (id !== "ignore") {
                var name = the_function.context[id];
                if (name.function === the_function) {
                    if (name.used === 0 && (
                        name.role !== "function"
                        || name.function.arity !== "unary"
                    )) {
                        warn("unused_a", name);
                    } else if (!name.init) {
                        warn("uninitialized_a", name);
                    }
                }
            }
        });
    }

    function uninitialized_and_unused() {

// Delve into the functions looking for variables that were not initialized
// or used. If the file imports or exports, then its global object is also
// delved.

        if (module_mode === true || option.node) {
            delve(global);
        }
        functions.forEach(delve);
    }

// Go through the token list, looking at usage of whitespace.

    function whitage() {
        var closer = "(end)";
        var free = false;
        var left = global;
        var margin = 0;
        var nr_comments_skipped = 0;
        var open = true;
        var qmark = "";
        var result;
        var right;

        function expected_at(at) {
            warn(
                "expected_a_at_b_c",
                right,
                artifact(right),
                fudge + at,
                artifact_column(right)
            );
        }

        function at_margin(fit) {
            var at = margin + fit;
            if (right.from !== at) {
                return expected_at(at);
            }
        }

        function no_space_only() {
            if (
                left.id !== "(global)"
                && left.nr + 1 === right.nr
                && (
                    left.line !== right.line
                    || left.thru !== right.from
                )
            ) {
                warn(
                    "unexpected_space_a_b",
                    right,
                    artifact(left),
                    artifact(right)
                );
            }
        }

        function no_space() {
            if (left.line === right.line) {
                if (left.thru !== right.from && nr_comments_skipped === 0) {
                    warn(
                        "unexpected_space_a_b",
                        right,
                        artifact(left),
                        artifact(right)
                    );
                }
            } else {
                if (open) {
                    var at = (free)
                        ? margin
                        : margin + 8;
                    if (right.from < at) {
                        expected_at(at);
                    }
                } else {
                    if (right.from !== margin + 8) {
                        expected_at(margin + 8);
                    }
                }
            }
        }

        function one_space_only() {
            if (left.line !== right.line || left.thru + 1 !== right.from) {
                warn(
                    "expected_space_a_b",
                    right,
                    artifact(left),
                    artifact(right)
                );
            }
        }

        function one_space() {
            if (left.line === right.line) {
                if (left.thru + 1 !== right.from && nr_comments_skipped === 0) {
                    warn(
                        "expected_space_a_b",
                        right,
                        artifact(left),
                        artifact(right)
                    );
                }
            } else {
                if (free) {
                    if (right.from < margin) {
                        expected_at(margin);
                    }
                } else {
                    var mislaid = (stack.length) > 0
                        ? stack[stack.length - 1].right
                        : undefined;
                    if (!open && mislaid !== undefined) {
                        warn(
                            "expected_a_next_at_b",
                            mislaid,
                            artifact(mislaid.id),
                            margin + 4 + fudge
                        );
                    } else if (right.from !== margin + 8) {
                        expected_at(margin + 8);
                    }
                }
            }
        }

        function unqmark() {

// Undo the effects of dangling nested ternary operators.

            var level = qmark.length;
            if (level > 0) {
                margin -= level * 4;
            }
            qmark = "";
        }

        stack = [];
        tokens.forEach(function (the_token) {
            right = the_token;
            if (right.id === "(comment)" || right.id === "(end)") {
                nr_comments_skipped += 1;
            } else {

// If left is an opener and right is not the closer, then push the previous
// state. If the token following the opener is on the next line, then this is
// an open form. If the tokens are on the same line, then it is a closed form.
// Open form is more readable, with each item (statement, argument, parameter,
// etc) starting on its own line. Closed form is more compact. Statement blocks
// are always in open form.

                var new_closer = opener[left.id];
                if (typeof new_closer === "string") {
                    if (new_closer !== right.id) {
                        stack.push({
                            closer: closer,
                            free: free,
                            margin: margin,
                            open: open,
                            qmark: qmark,
                            right: right
                        });
                        qmark = "";
                        closer = new_closer;
                        if (left.line !== right.line) {
                            free = closer === ")" && left.free;
                            open = true;
                            margin += 4;
                            if (right.role === "label") {
                                if (right.from !== 0) {
                                    expected_at(0);
                                }
                            } else if (right.switch) {
                                unqmark();
                                at_margin(-4);
                            } else {
                                at_margin(0);
                            }
                        } else {
                            if (right.statement || right.role === "label") {
                                warn(
                                    "expected_line_break_a_b",
                                    right,
                                    artifact(left),
                                    artifact(right)
                                );
                            }
                            free = false;
                            open = false;
                            no_space_only();
                        }
                    } else {

// If left and right are opener and closer, then the placement of right depends
// on the openness. Illegal pairs (like {]) have already been detected.

                        if (left.line === right.line) {
                            no_space();
                        } else {
                            at_margin(0);
                        }
                    }
                } else {

// If right is a closer, then pop the previous state.

                    if (right.id === closer) {
                        var previous = stack.pop();
                        margin = previous.margin;
                        if (open && right.id !== ";") {
                            at_margin(0);
                        } else {
                            no_space_only();
                        }
                        closer = previous.closer;
                        free = previous.free;
                        open = previous.open;
                        qmark = previous.qmark;
                    } else {

// Left is not an opener, and right is not a closer. The nature of left and
// right will determine the space between them.

// If left is , or ; or right is a statement then if open, right must go at the
// margin, or if closed, a space between.


                        if (right.switch) {
                            unqmark();
                            at_margin(-4);
                        } else if (right.role === "label") {
                            if (right.from !== 0) {
                                expected_at(0);
                            }
                        } else if (left.id === ",") {
                            unqmark();
                            if (!open || (
                                (free || closer === "]")
                                && left.line === right.line
                            )) {
                                one_space();
                            } else {
                                at_margin(0);
                            }

// If right is a ternary operator, line it up on the margin. Use qmark to
// deal with nested ternary operators.

                        } else if (right.arity === "ternary") {
                            if (right.id === "?") {
                                margin += 4;
                                qmark += "?";
                            } else {
                                result = qmark.match(rx_colons);
                                qmark = result[1] + ":";
                                margin -= 4 * result[2].length;
                            }
                            at_margin(0);
                        } else if (
                            right.arity === "binary"
                            && right.id === "("
                            && free
                        ) {
                            no_space();
                        } else if (
                            left.id === "."
                            || left.id === "..."
                            || right.id === ","
                            || right.id === ";"
                            || right.id === ":"
                            || (right.arity === "binary" && (
                                right.id === "("
                                || right.id === "["
                            ))
                            || (
                                right.arity === "function"
                                && left.id !== "function"
                            )
                        ) {
                            no_space_only();
                        } else if (right.id === ".") {
                            if (left.line === right.line) {
                                no_space();
                            } else {
                                if (!rx_dot.test(qmark)) {
                                    qmark += ".";
                                    margin += 4;
                                }
                                at_margin(0);
                            }
                        } else if (left.id === ";") {
                            unqmark();
                            if (open) {
                                at_margin(0);
                            } else {
                                one_space();
                            }
                        } else if (
                            left.arity === "ternary"
                            || left.id === "case"
                            || left.id === "catch"
                            || left.id === "else"
                            || left.id === "finally"
                            || left.id === "while"
                            || right.id === "catch"
                            || right.id === "else"
                            || right.id === "finally"
                            || (right.id === "while" && !right.statement)
                            || (left.id === ")" && right.id === "{")
                        ) {
                            one_space_only();
                        } else if (right.statement === true) {
                            if (open) {
                                at_margin(0);
                            } else {
                                one_space();
                            }
                        } else if (
                            left.id === "var"
                            || left.id === "const"
                            || left.id === "let"
                        ) {
                            stack.push({
                                closer: closer,
                                free: free,
                                margin: margin,
                                open: open,
                                qmark: qmark
                            });
                            closer = ";";
                            free = false;
                            open = left.open;
                            qmark = "";
                            if (open) {
                                margin = margin + 4;
                                at_margin(0);
                            } else {
                                one_space_only();
                            }
                        } else if (

// There is a space between left and right.

                            spaceop[left.id] === true
                            || spaceop[right.id] === true
                            || (
                                left.arity === "binary"
                                && (left.id === "+" || left.id === "-")
                            )
                            || (
                                right.arity === "binary"
                                && (right.id === "+" || right.id === "-")
                            )
                            || left.id === "function"
                            || left.id === ":"
                            || (
                                (
                                    left.identifier
                                    || left.id === "(string)"
                                    || left.id === "(number)"
                                )
                                && (
                                    right.identifier
                                    || right.id === "(string)"
                                    || right.id === "(number)"
                                )
                            )
                            || (left.arity === "statement" && right.id !== ";")
                        ) {
                            one_space();
                        } /*else if (left.arity === "unary" && left.id !== "`") {
                            no_space_only();
                        }*/
                    }
                }
                nr_comments_skipped = 0;
                delete left.calls;
                delete left.dead;
                delete left.free;
                delete left.init;
                delete left.open;
                delete left.used;
                left = right;
            }
        });
    }

// The jslint function itself.

    return function (source, option_object, global_array) {
        try {
            warnings = [];
            option = Object.assign(empty(), option_object);
            anon = "anonymous";
            block_stack = [];
            declared_globals = empty();
            directive_mode = true;
            directives = [];
            early_stop = true;
            exports = empty();
            froms = [];
            fudge = (option.fudge)
                ? 1
                : 0;
            functions = [];
            global = {
                id: "(global)",
                body: true,
                context: empty(),
                from: 0,
                level: 0,
                line: 0,
                live: [],
                loop: 0,
                switch: 0,
                thru: 0
            };
            blockage = global;
            functionage = global;
            json_mode = false;
//            mega_mode = false;
            module_mode = false;
            next_token = global;
            property = empty();
            stack = [];
            tenure = undefined;
            token = global;
            token_nr = 0;
            var_mode = undefined;
            populate(declared_globals, standard, false);
            if (global_array !== undefined) {
                populate(declared_globals, global_array, false);
            }
            Object.keys(option).forEach(function (name) {
                if (option[name] === true) {
                    var allowed = allowed_option[name];
                    if (Array.isArray(allowed)) {
                        populate(declared_globals, allowed, false);
                    }
                }
            });
            tokenize(source);
            advance();
            if (json_mode) {
                tree = json_value();
                advance("(end)");
            } else {

// Because browsers encourage combining of script files, the first token might
// be a semicolon to defend against a missing semicolon in the preceding file.

                if (option.browser) {
                    if (next_token.id === ";") {
                        advance(";");
                    }
                } else {

// If we are not in a browser, then the file form of strict pragma may be used.

                    if (
                        next_token.value === "use strict"
                    ) {
                        global.strict = next_token;
                        advance("(string)");
                        advance(";");
                    }
                }
                tree = statements();
                advance("(end)");
                functionage = global;
                walk_statement(tree);
                if (module_mode && global.strict !== undefined) {
                    warn("unexpected_a", global.strict);
                }
                uninitialized_and_unused();
                if (!option.white) {
                    whitage();
                }
            }
            if (!option.browser) {
                directives.forEach(function (comment) {
                    if (comment.directive === "global") {
                        warn("missing_browser", comment);
                    }
                });
            }
            early_stop = false;
        } catch (e) {
            if (e.name !== "JSLintError") {
                warnings.push(e);
            }
        }
        return {
            directives: directives,
            edition: "2017-11-06",
            exports: exports,
            froms: froms,
            functions: functions,
            global: global,
            id: "(JSLint)",
            json: json_mode,
            lines: lines,
            module: module_mode === true,
            ok: warnings.length === 0 && !early_stop,
            option: option,
            property: property,
            stop: early_stop,
            tokens: tokens,
            tree: tree,
            warnings: warnings.sort(function (a, b) {
                return a.line - b.line || a.column - b.column;
            })
        };
    };
}());

function SYNCJSLINT(argc, argv)
{
	var already_loaded={};
	function LOADFILE(lines, index, pos, fname, paths, options, require)
	{
		var i;
		var tmp, tmp2;
		var offset=0;
		var multiline_comment = false;

		if(require || options.multiload) {
			if(already_loaded[fname] !== undefined) {
				writeln("Skipping "+fname);
				return(0);
			}
		}
		already_loaded[fname]=true;
		if(!file_exists(fname)) {
			tmp=file_getname(fname);
			for(i=0; i<paths.length; i++) {
				if(file_exists(paths[i]+tmp)) {
					fname=paths[i]+tmp;
					break;
				}
			}
		}

		var all_lines;
		var f=new File(fname);
		var m;

		writeln("Loading "+fname);
		if(f.open("r",65535)) {
			all_lines=f.readAll(65535);
			for(i=0; i<all_lines.length; i++) {
				lines.splice(pos+offset+i, 0, all_lines[i]);
				index.splice(pos+offset+i, 0, fname+":"+(i+1));
	
				/* TODO: smart string parsing... */
				tmp=all_lines[i];
				if (multiline_comment) {
					if ((tmp2 = tmp.replace(/^.*?\*\//,'')) != tmp) {
						tmp = tmp2;
						multiline_comment = false;
					}
					else {
						tmp = '';
					}
				}
				tmp=tmp.replace(/\/\*.*?\*\//g,'');
				tmp=tmp.replace(/\/\/.*^/,'');
				if ((tmp2 = tmp.replace(/\/\*.*$/, '')) != tmp) {
					tmp = tmp2;
					multiline_comment = true;
				}
				if((m=tmp.match(/^\s*require\([^"']*(['"])(.*?[^\\])\1.*?\)/))!==null) {
					offset+=LOADFILE(lines,index,pos+offset+i,m[2],paths,options,true);
				}
				else if((m=tmp.match(/^\s*load\([^"']*(['"])require.js\1[^"']*,.*?,(['"])(.*?[^\\])\2.*?\)/))!==null) {
					offset+=LOADFILE(lines,index,pos+offset+i,m[3],paths,options,true);
				}
				else if((m=tmp.match(/^\s*load\([^"']*(['"])(.*?[^\\])\1.*?\)/))!==null) {
					offset+=LOADFILE(lines,index,pos+offset+i,m[2],paths,options,false);
				}
	
			}
			f.close();
			return(all_lines.length+offset);
		}
		else {
			writeln("!!Load failed");
		}
		return(0);
	}

	var paths=[backslash(system.exec_dir),backslash(backslash(system.exec_dir)+'load')];
	var all_lines;
	var index;
	var options={
		multiload:false,
		white:true,		// Allow both spaces and tabs for indent
		single:true,		// Allow both single and double quotes
		Synchronet:true
	};
	var myResult;
	var tmpVar1;
	var tmpVar2;
	var tmpVar3;
	var i;
	var optdone=false;

	function dumparray(obj,indent) {
		var prop;
		var ind=format("%*s",indent,'');

		writeln(ind+obj.sort().join("\r\n"+ind));
	}

	function dumpobjs(obj,indent) {
		var prop;
		var ind=format("%*s",indent,'');

		if(obj.constructor.toString().substr(0,16) == 'function Array()')
			dumparray(obj,indent);
		else {
			for(prop in obj) {
				if(typeof(obj[prop])=='object') {
					writeln(ind+prop+":");
					if(obj[prop].constructor.toString().substr(0,16) == 'function Array()')
						dumparray(obj[prop],indent+2);
					else
						dumpobjs(obj[prop],indent+2);
				}
				else {
					writeln(ind+prop+": "+obj[prop]);
				}
			}
		}
	}

	function dumpFunc(obj) {
		var prop;
		var indent=2;
		var ind=format("%*s",indent,'');

		writeln(ind+obj.name+"("+(obj.params===undefined?'':obj.params)+"):");
		indent+=2;
		ind=format("%*s",indent,'');
		writeln(ind+index[obj.line]);
		for(prop in obj) {
			switch(prop) {
				case 'name':
				case 'line':
				case 'params':
					break;
				default:
					if(typeof(obj[prop])=='object') {
						writeln(ind+prop+":");
						dumpobjs(obj[prop],indent+2);
					}
					else {
						writeln(ind+prop+": "+obj[prop]);
					}
			}
		}
	}

	function dumpFuncs(obj) {
		var f;
		var indent=0;
		var ind=format("%*s",indent,'');

		if(obj !== undefined) {
			writeln("Functions:");
			for(f in obj) {
				dumpFunc(obj[f]);
				writeln('');
			}
		}
	}

	function dumpGlobals(obj) {
		var gl;

		if(obj !== undefined) {
			writeln('Defined Globals:');
			writeln('  '+obj.sort().join("\r\n  "));
		}
	}

	function dumpJSON(obj) {
		if(obj !== undefined) {
			writeln('JSON: '+obj);
		}
	}

	for(tmpVar1 in argv) {
		if(tmpVar1.search(/^[0-9]+$/)!=-1) {
			if(!optdone) {
				if(argv[tmpVar1]=='--') {
					optdone=true;
					continue;
				}
				if(argv[tmpVar1].substr(0,3)=='-l:') {
					myResult=argv[tmpVar1].substr(3).split(/:/);
					for(tmpVar2 in myResult) {
						if(tmpVar2.search(/^[0-9]+$/)!=-1) {
							paths.push(backslash(myResult[tmpVar2]));
						}
					}
					continue;
				}
				if(argv[tmpVar1].substr(0,3)=='-L:') {
					myResult=argv[tmpVar1].substr(3).split(/:/);
					for(tmpVar2 in myResult) {
						if(tmpVar2.search(/^[0-9]+$/)!=-1) {
							paths.unshift(backslash(myResult[tmpVar2]));
						}
					}
					continue;
				}
				if(argv[tmpVar1].charAt(0)=='-') {
					if(options[argv[tmpVar1].substr(1)] === undefined)
						options[argv[tmpVar1].substr(1)]=true;
					else
						options[argv[tmpVar1].substr(1)]=!options[argv[tmpVar1].substr(1)];
					continue;
				}
			}

			all_lines=[];
			index=[];
			LOADFILE(all_lines, index, 0, argv[tmpVar1], paths, options);
			writeln("Linting...");
			myResult=jslint(all_lines,options,[]);
			if(!myResult.ok) {
				for(i in myResult.warnings) {
					tmpVar2 = myResult.warnings[i];
					writeln("-----");
					tmpVar2.message = tmpVar2.message.replace(/\bline ([0-9]+)/g, function(m, l) {
						return index[parseInt(l, 10)];
					});
					writeln(index[tmpVar2.line]+"@"+tmpVar2.column+": "+tmpVar2.message);
					writeln(all_lines[tmpVar2.line]);
					writeln(format("%.*s",tmpVar2.column,all_lines[tmpVar2.line]).replace(/[^\s]/g,' ')+'^');
				}
				if (myResult.stop) {
					writeln("-----");
					writeln('Processing STOPPED');
				}
				else {
					writeln('');
					writeln(myResult.warnings.length + ' Warnings');
				}
			}

			if(options.report) {
				if(myResult===undefined) {
					writeln("Report NOT generated");
				}
				else {
					dumpGlobals(myResult.global);
					writeln('');
					dumpFuncs(myResult.functions);
				}
			}
		}
	}
}

SYNCJSLINT(argc, argv);
