function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
    var shortcuts = {};
    var tokenizer;
    (function () {
      var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
      var allRegexs = [];
      var regexKeys = {};
      for (var i = 0, n = allPatterns.length; i < n; ++i) {
        var patternParts = allPatterns[i];
        var shortcutChars = patternParts[3];
        if (shortcutChars) {
          for (var c = shortcutChars.length; --c >= 0;) {
            shortcuts[shortcutChars.charAt(c)] = patternParts;
          }
        }
        var regex = patternParts[1];
        var k = '' + regex;
        if (!regexKeys.hasOwnProperty(k)) {
          allRegexs.push(regex);
          regexKeys[k] = null;
        }
      }
      allRegexs.push(/[\0-\uffff]/);
      tokenizer = combinePrefixPatterns(allRegexs);
    })();

    var nPatterns = fallthroughStylePatterns.length;
    var notWs = /\S/;

    /**
     * Lexes job.source and produces an output array job.decorations of style
     * classes preceded by the position at which they start in job.source in
     * order.
     *
     * @param {Object} job an object like {@code
     *    source: {string} sourceText plain text,
     *    basePos: {int} position of job.source in the larger chunk of
     *        sourceCode.
     * }
     */
    var decorate = function (job) {
      var sourceCode = job.source, basePos = job.basePos;
      /** Even entries are positions in source in ascending order.  Odd enties
        * are style markers (e.g., PR_COMMENT) that run from that position until
        * the end.
        * @type {Array.<number|string>}
        */
      var decorations = [basePos, PR_PLAIN];
      var pos = 0;  // index into sourceCode
      var tokens = sourceCode.match(tokenizer) || [];
      var styleCache = {};

      for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
        var token = tokens[ti];
        var style = styleCache[token];
        var match = void 0;

        var isEmbedded;
        if (typeof style === 'string') {
          isEmbedded = false;
        } else {
          var patternParts = shortcuts[token.charAt(0)];
          if (patternParts) {
            match = token.match(patternParts[1]);
            style = patternParts[0];
          } else {
            for (var i = 0; i < nPatterns; ++i) {
              patternParts = fallthroughStylePatterns[i];
              match = token.match(patternParts[1]);
              if (match) {
                style = patternParts[0];
                break;
              }
            }

            if (!match) {  // make sure that we make progress
              style = PR_PLAIN;
            }
          }

          isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
          if (isEmbedded && !(match && typeof match[1] === 'string')) {
            isEmbedded = false;
            style = PR_SOURCE;
          }

          if (!isEmbedded) { styleCache[token] = style; }
        }

        var tokenStart = pos;
        pos += token.length;

        if (!isEmbedded) {
          decorations.push(basePos + tokenStart, style);
        } else {  // Treat group 1 as an embedded block of source code.
          var embeddedSource = match[1];
          var embeddedSourceStart = token.indexOf(embeddedSource);
          var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
          if (match[2]) {
            // If embeddedSource can be blank, then it would match at the
            // beginning which would cause us to infinitely recurse on the
            // entire token, so we catch the right context in match[2].
            embeddedSourceEnd = token.length - match[2].length;
            embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
          }
          var lang = style.substring(5);
          // Decorate the left of the embedded source
          appendDecorations(
              basePos + tokenStart,
              token.substring(0, embeddedSourceStart),
              decorate, decorations);
          // Decorate the embedded source
          appendDecorations(
              basePos + tokenStart + embeddedSourceStart,
              embeddedSource,
              langHandlerForExtension(lang, embeddedSource),
              decorations);
          // Decorate the right of the embedded section
          appendDecorations(
              basePos + tokenStart + embeddedSourceEnd,
              token.substring(embeddedSourceEnd),
              decorate, decorations);
        }
      }
      job.decorations = decorations;
    };
    return decorate;
  }
Given triples of [style, pattern, context] returns a lexing function,
The lexing function interprets the patterns to find token boundaries and
returns a decoration list of the form
[index_0, style_0, index_1, style_1, ..., index_n, style_n]
where index_n is an index into the sourceCode, and style_n is a style
constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
all characters in sourceCode[index_n-1:index_n].

The stylePatterns is a list whose elements have the form
[style : string, pattern : RegExp, DEPRECATED, shortcut : string].

Style is a style constant like PR_PLAIN, or can be a string of the
form 'lang-FOO', where FOO is a language extension describing the
language of the portion of the token in $1 after pattern executes.
E.g., if style is 'lang-lisp', and group 1 contains the text
'(hello (world))', then that portion of the token will be passed to the
registered lisp handler for formatting.
The text before and after group 1 will be restyled using this decorator
so decorators should take care that this doesn't result in infinite
recursion.  For example, the HTML lexer rule for SCRIPT elements looks
something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
'<script>foo()<\/script>', which would cause the current decorator to
be called with '<script>' which would not match the same rule since
group 1 must not be empty, so it would be instead styled as PR_TAG by
the generic tag rule.  The handler registered for the 'js' extension would
then be called with 'foo()', and finally, the current decorator would
be called with '<\/script>' which would not match the original rule and
so the generic tag rule would identify it as a tag.

Pattern must only match prefixes, and if it matches a prefix, then that
match is considered a token with the same style.

Context is applied to the last non-whitespace, non-comment token
recognized.

Shortcut is an optional string of characters, any of which, if the first
character, gurantee that this pattern and only this pattern matches.

@param {Array} shortcutStylePatterns patterns that always start with
  a known character.  Must have a shortcut string.
@param {Array} fallthroughStylePatterns patterns that will be tried in
  order if the shortcut ones fail.  May have shortcuts.

@return {function (Object)} a
  function that takes source code and returns a list of decorations.