function lex()

in web/jslint/src/main/resources/data/jslint-2020-03-28.js [1115:1358]


  function lex() {
    var array;
    var i = 0;
    var j = 0;
    var last;
    var result;
    var the_token; // This should properly be a tail recursive function, but sadly, conformant
    // implementations of ES6 are still rare. This is the ideal code:
    //      if (!source_line) {
    //          source_line = next_line();
    //          from = 0;
    //          return (
    //              source_line === undefined
    //              ? (
    //                  mega_mode
    //                  ? stop_at("unclosed_mega", mega_line, mega_from)
    //                  : make("(end)")
    //              )
    //              : lex()
    //          );
    //      }
    // Unfortunately, incompetent JavaScript engines will sometimes fail to execute
    // it correctly. So for now, we do it the old fashioned way.

    while (!source_line) {
      source_line = next_line();
      from = 0;

      if (source_line === undefined) {
        return mega_mode ? stop_at("unclosed_mega", mega_line, mega_from) : make("(end)");
      }
    }

    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[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 a string.


    if (snippet === "\"") {
      return string(snippet);
    }

    if (snippet === "'") {
      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.


    if (snippet === "`") {
      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[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[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.


    if (snippet === "//") {
      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.


    if (snippet === "/*") {
      array = [];

      if (source_line[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.


    if (snippet === "/") {
      // 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) {
          if (prior.id === "return") {
            return regexp();
          }

          if (prior.id === "(begin)" || prior.id === "case" || prior.id === "delete" || prior.id === "in" || prior.id === "instanceof" || prior.id === "new" || prior.id === "typeof" || prior.id === "void" || prior.id === "yield") {
            the_token = regexp();
            return stop("unexpected_a", the_token);
          }
        }
      } else {
        last = prior.id[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[0] === "/") {
        column += 1;
        source_line = source_line.slice(1);
        snippet = "/=";
        warn_at("unexpected_a", line, column, "/=");
      }
    }

    return make(snippet);
  }