nitdoc: remove `require.js` dependency
[nit.git] / share / nitdoc / js / lib / highlight.js
index 54f02a6..bd277ee 100644 (file)
        SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
-define([], function () {
-       var hljs = new function() {
-
-         /* Utility functions */
-
-         function escape(value) {
-               return value.replace(/&/gm, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');
+var hljs = new function() {
+
+  /* Utility functions */
+
+  function escape(value) {
+       return value.replace(/&/gm, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');
+  }
+
+  function tag(node) {
+       return node.nodeName.toLowerCase();
+  }
+
+  function testRe(re, lexeme) {
+       var match = re && re.exec(lexeme);
+       return match && match.index == 0;
+  }
+
+  function blockLanguage(block) {
+       var classes = (block.className + ' ' + (block.parentNode ? block.parentNode.className : '')).split(/\s+/);
+       classes = classes.map(function(c) {return c.replace(/^lang(uage)?-/, '');});
+       return classes.filter(function(c) {return getLanguage(c) || c == 'no-highlight';})[0];
+  }
+
+  function inherit(parent, obj) {
+       var result = {};
+       for (var key in parent)
+         result[key] = parent[key];
+       if (obj)
+         for (var key in obj)
+           result[key] = obj[key];
+       return result;
+  };
+
+  /* Stream merging */
+
+  function nodeStream(node) {
+       var result = [];
+       (function _nodeStream(node, offset) {
+         for (var child = node.firstChild; child; child = child.nextSibling) {
+           if (child.nodeType == 3)
+             offset += child.nodeValue.length;
+           else if (tag(child) == 'br')
+             offset += 1;
+           else if (child.nodeType == 1) {
+             result.push({
+               event: 'start',
+               offset: offset,
+               node: child
+             });
+             offset = _nodeStream(child, offset);
+             result.push({
+               event: 'stop',
+               offset: offset,
+               node: child
+             });
+           }
          }
-
-         function tag(node) {
-               return node.nodeName.toLowerCase();
+         return offset;
+       })(node, 0);
+       return result;
+  }
+
+  function mergeStreams(original, highlighted, value) {
+       var processed = 0;
+       var result = '';
+       var nodeStack = [];
+
+       function selectStream() {
+         if (!original.length || !highlighted.length) {
+           return original.length ? original : highlighted;
          }
-
-         function testRe(re, lexeme) {
-               var match = re && re.exec(lexeme);
-               return match && match.index == 0;
+         if (original[0].offset != highlighted[0].offset) {
+           return (original[0].offset < highlighted[0].offset) ? original : highlighted;
          }
 
-         function blockLanguage(block) {
-               var classes = (block.className + ' ' + (block.parentNode ? block.parentNode.className : '')).split(/\s+/);
-               classes = classes.map(function(c) {return c.replace(/^lang(uage)?-/, '');});
-               return classes.filter(function(c) {return getLanguage(c) || c == 'no-highlight';})[0];
+         /*
+         To avoid starting the stream just before it should stop the order is
+         ensured that original always starts first and closes last:
+
+         if (event1 == 'start' && event2 == 'start')
+           return original;
+         if (event1 == 'start' && event2 == 'stop')
+           return highlighted;
+         if (event1 == 'stop' && event2 == 'start')
+           return original;
+         if (event1 == 'stop' && event2 == 'stop')
+           return highlighted;
+
+         ... which is collapsed to:
+         */
+         return highlighted[0].event == 'start' ? original : highlighted;
+       }
+
+       function open(node) {
+         function attr_str(a) {return ' ' + a.nodeName + '="' + escape(a.value) + '"';}
+         result += '<' + tag(node) + Array.prototype.map.call(node.attributes, attr_str).join('') + '>';
+       }
+
+       function close(node) {
+         result += '</' + tag(node) + '>';
+       }
+
+       function render(event) {
+         (event.event == 'start' ? open : close)(event.node);
+       }
+
+       while (original.length || highlighted.length) {
+         var stream = selectStream();
+         result += escape(value.substr(processed, stream[0].offset - processed));
+         processed = stream[0].offset;
+         if (stream == original) {
+           /*
+           On any opening or closing tag of the original markup we first close
+           the entire highlighted node stack, then render the original tag along
+           with all the following original tags at the same offset and then
+           reopen all the tags on the highlighted stack.
+           */
+           nodeStack.reverse().forEach(close);
+           do {
+             render(stream.splice(0, 1)[0]);
+             stream = selectStream();
+           } while (stream == original && stream.length && stream[0].offset == processed);
+           nodeStack.reverse().forEach(open);
+         } else {
+           if (stream[0].event == 'start') {
+             nodeStack.push(stream[0].node);
+           } else {
+             nodeStack.pop();
+           }
+           render(stream.splice(0, 1)[0]);
          }
-
-         function inherit(parent, obj) {
-               var result = {};
-               for (var key in parent)
-                 result[key] = parent[key];
-               if (obj)
-                 for (var key in obj)
-                   result[key] = obj[key];
-               return result;
-         };
-
-         /* Stream merging */
-
-         function nodeStream(node) {
-               var result = [];
-               (function _nodeStream(node, offset) {
-                 for (var child = node.firstChild; child; child = child.nextSibling) {
-                   if (child.nodeType == 3)
-                     offset += child.nodeValue.length;
-                   else if (tag(child) == 'br')
-                     offset += 1;
-                   else if (child.nodeType == 1) {
-                     result.push({
-                       event: 'start',
-                       offset: offset,
-                       node: child
-                     });
-                     offset = _nodeStream(child, offset);
-                     result.push({
-                       event: 'stop',
-                       offset: offset,
-                       node: child
-                     });
-                   }
-                 }
-                 return offset;
-               })(node, 0);
-               return result;
+       }
+       return result + escape(value.substr(processed));
+  }
+
+  /* Initialization */
+
+  function compileLanguage(language) {
+
+       function reStr(re) {
+           return (re && re.source) || re;
+       }
+
+       function langRe(value, global) {
+         return RegExp(
+           reStr(value),
+           'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '')
+         );
+       }
+
+       function compileMode(mode, parent) {
+         if (mode.compiled)
+           return;
+         mode.compiled = true;
+
+         mode.keywords = mode.keywords || mode.beginKeywords;
+         if (mode.keywords) {
+           var compiled_keywords = {};
+
+           function flatten(className, str) {
+             if (language.case_insensitive) {
+               str = str.toLowerCase();
+             }
+             str.split(' ').forEach(function(kw) {
+               var pair = kw.split('|');
+               compiled_keywords[pair[0]] = [className, pair[1] ? Number(pair[1]) : 1];
+             });
+           }
+
+           if (typeof mode.keywords == 'string') { // string
+             flatten('keyword', mode.keywords);
+           } else {
+             Object.keys(mode.keywords).forEach(function (className) {
+               flatten(className, mode.keywords[className]);
+             });
+           }
+           mode.keywords = compiled_keywords;
          }
-
-         function mergeStreams(original, highlighted, value) {
-               var processed = 0;
-               var result = '';
-               var nodeStack = [];
-
-               function selectStream() {
-                 if (!original.length || !highlighted.length) {
-                   return original.length ? original : highlighted;
-                 }
-                 if (original[0].offset != highlighted[0].offset) {
-                   return (original[0].offset < highlighted[0].offset) ? original : highlighted;
-                 }
-
-                 /*
-                 To avoid starting the stream just before it should stop the order is
-                 ensured that original always starts first and closes last:
-
-                 if (event1 == 'start' && event2 == 'start')
-                   return original;
-                 if (event1 == 'start' && event2 == 'stop')
-                   return highlighted;
-                 if (event1 == 'stop' && event2 == 'start')
-                   return original;
-                 if (event1 == 'stop' && event2 == 'stop')
-                   return highlighted;
-
-                 ... which is collapsed to:
-                 */
-                 return highlighted[0].event == 'start' ? original : highlighted;
-               }
-
-               function open(node) {
-                 function attr_str(a) {return ' ' + a.nodeName + '="' + escape(a.value) + '"';}
-                 result += '<' + tag(node) + Array.prototype.map.call(node.attributes, attr_str).join('') + '>';
-               }
-
-               function close(node) {
-                 result += '</' + tag(node) + '>';
-               }
-
-               function render(event) {
-                 (event.event == 'start' ? open : close)(event.node);
-               }
-
-               while (original.length || highlighted.length) {
-                 var stream = selectStream();
-                 result += escape(value.substr(processed, stream[0].offset - processed));
-                 processed = stream[0].offset;
-                 if (stream == original) {
-                   /*
-                   On any opening or closing tag of the original markup we first close
-                   the entire highlighted node stack, then render the original tag along
-                   with all the following original tags at the same offset and then
-                   reopen all the tags on the highlighted stack.
-                   */
-                   nodeStack.reverse().forEach(close);
-                   do {
-                     render(stream.splice(0, 1)[0]);
-                     stream = selectStream();
-                   } while (stream == original && stream.length && stream[0].offset == processed);
-                   nodeStack.reverse().forEach(open);
-                 } else {
-                   if (stream[0].event == 'start') {
-                     nodeStack.push(stream[0].node);
-                   } else {
-                     nodeStack.pop();
-                   }
-                   render(stream.splice(0, 1)[0]);
-                 }
-               }
-               return result + escape(value.substr(processed));
+         mode.lexemesRe = langRe(mode.lexemes || /\b[A-Za-z0-9_]+\b/, true);
+
+         if (parent) {
+           if (mode.beginKeywords) {
+             mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')\\b';
+           }
+           if (!mode.begin)
+             mode.begin = /\B|\b/;
+           mode.beginRe = langRe(mode.begin);
+           if (!mode.end && !mode.endsWithParent)
+             mode.end = /\B|\b/;
+           if (mode.end)
+             mode.endRe = langRe(mode.end);
+           mode.terminator_end = reStr(mode.end) || '';
+           if (mode.endsWithParent && parent.terminator_end)
+             mode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end;
          }
-
-         /* Initialization */
-
-         function compileLanguage(language) {
-
-               function reStr(re) {
-                   return (re && re.source) || re;
-               }
-
-               function langRe(value, global) {
-                 return RegExp(
-                   reStr(value),
-                   'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '')
-                 );
-               }
-
-               function compileMode(mode, parent) {
-                 if (mode.compiled)
-                   return;
-                 mode.compiled = true;
-
-                 mode.keywords = mode.keywords || mode.beginKeywords;
-                 if (mode.keywords) {
-                   var compiled_keywords = {};
-
-                   function flatten(className, str) {
-                     if (language.case_insensitive) {
-                       str = str.toLowerCase();
-                     }
-                     str.split(' ').forEach(function(kw) {
-                       var pair = kw.split('|');
-                       compiled_keywords[pair[0]] = [className, pair[1] ? Number(pair[1]) : 1];
-                     });
-                   }
-
-                   if (typeof mode.keywords == 'string') { // string
-                     flatten('keyword', mode.keywords);
-                   } else {
-                     Object.keys(mode.keywords).forEach(function (className) {
-                       flatten(className, mode.keywords[className]);
-                     });
-                   }
-                   mode.keywords = compiled_keywords;
-                 }
-                 mode.lexemesRe = langRe(mode.lexemes || /\b[A-Za-z0-9_]+\b/, true);
-
-                 if (parent) {
-                   if (mode.beginKeywords) {
-                     mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')\\b';
-                   }
-                   if (!mode.begin)
-                     mode.begin = /\B|\b/;
-                   mode.beginRe = langRe(mode.begin);
-                   if (!mode.end && !mode.endsWithParent)
-                     mode.end = /\B|\b/;
-                   if (mode.end)
-                     mode.endRe = langRe(mode.end);
-                   mode.terminator_end = reStr(mode.end) || '';
-                   if (mode.endsWithParent && parent.terminator_end)
-                     mode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end;
-                 }
-                 if (mode.illegal)
-                   mode.illegalRe = langRe(mode.illegal);
-                 if (mode.relevance === undefined)
-                   mode.relevance = 1;
-                 if (!mode.contains) {
-                   mode.contains = [];
-                 }
-                 var expanded_contains = [];
-                 mode.contains.forEach(function(c) {
-                   if (c.variants) {
-                     c.variants.forEach(function(v) {expanded_contains.push(inherit(c, v));});
-                   } else {
-                     expanded_contains.push(c == 'self' ? mode : c);
-                   }
-                 });
-                 mode.contains = expanded_contains;
-                 mode.contains.forEach(function(c) {compileMode(c, mode);});
-
-                 if (mode.starts) {
-                   compileMode(mode.starts, parent);
-                 }
-
-                 var terminators =
-                   mode.contains.map(function(c) {
-                     return c.beginKeywords ? '\\.?(' + c.begin + ')\\.?' : c.begin;
-                   })
-                   .concat([mode.terminator_end, mode.illegal])
-                   .map(reStr)
-                   .filter(Boolean);
-                 mode.terminators = terminators.length ? langRe(terminators.join('|'), true) : {exec: function(s) {return null;}};
-
-                 mode.continuation = {};
-               }
-
-               compileMode(language);
+         if (mode.illegal)
+           mode.illegalRe = langRe(mode.illegal);
+         if (mode.relevance === undefined)
+           mode.relevance = 1;
+         if (!mode.contains) {
+           mode.contains = [];
          }
-
-         /*
-         Core highlighting function. Accepts a language name, or an alias, and a
-         string with the code to highlight. Returns an object with the following
-         properties:
-
-         - relevance (int)
-         - value (an HTML string with highlighting markup)
-
-         */
-         function highlight(name, value, ignore_illegals, continuation) {
-
-               function subMode(lexeme, mode) {
-                 for (var i = 0; i < mode.contains.length; i++) {
-                   if (testRe(mode.contains[i].beginRe, lexeme)) {
-                     return mode.contains[i];
-                   }
-                 }
-               }
-
-               function endOfMode(mode, lexeme) {
-                 if (testRe(mode.endRe, lexeme)) {
-                   return mode;
-                 }
-                 if (mode.endsWithParent) {
-                   return endOfMode(mode.parent, lexeme);
-                 }
-               }
-
-               function isIllegal(lexeme, mode) {
-                 return !ignore_illegals && testRe(mode.illegalRe, lexeme);
-               }
-
-               function keywordMatch(mode, match) {
-                 var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0];
-                 return mode.keywords.hasOwnProperty(match_str) && mode.keywords[match_str];
-               }
-
-               function buildSpan(classname, insideSpan, leaveOpen, noPrefix) {
-                 var classPrefix = noPrefix ? '' : options.classPrefix,
-                     openSpan    = '<span class="' + classPrefix,
-                     closeSpan   = leaveOpen ? '' : '</span>';
-
-                 openSpan += classname + '">';
-
-                 return openSpan + insideSpan + closeSpan;
-               }
-
-               function processKeywords() {
-                 if (!top.keywords)
-                   return escape(mode_buffer);
-                 var result = '';
-                 var last_index = 0;
-                 top.lexemesRe.lastIndex = 0;
-                 var match = top.lexemesRe.exec(mode_buffer);
-                 while (match) {
-                   result += escape(mode_buffer.substr(last_index, match.index - last_index));
-                   var keyword_match = keywordMatch(top, match);
-                   if (keyword_match) {
-                     relevance += keyword_match[1];
-                     result += buildSpan(keyword_match[0], escape(match[0]));
-                   } else {
-                     result += escape(match[0]);
-                   }
-                   last_index = top.lexemesRe.lastIndex;
-                   match = top.lexemesRe.exec(mode_buffer);
-                 }
-                 return result + escape(mode_buffer.substr(last_index));
-               }
-
-               function processSubLanguage() {
-                 if (top.subLanguage && !languages[top.subLanguage]) {
-                   return escape(mode_buffer);
-                 }
-                 var result = top.subLanguage ? highlight(top.subLanguage, mode_buffer, true, top.continuation.top) : highlightAuto(mode_buffer);
-                 // Counting embedded language score towards the host language may be disabled
-                 // with zeroing the containing mode relevance. Usecase in point is Markdown that
-                 // allows XML everywhere and makes every XML snippet to have a much larger Markdown
-                 // score.
-                 if (top.relevance > 0) {
-                   relevance += result.relevance;
-                 }
-                 if (top.subLanguageMode == 'continuous') {
-                   top.continuation.top = result.top;
-                 }
-                 return buildSpan(result.language, result.value, false, true);
-               }
-
-               function processBuffer() {
-                 return top.subLanguage !== undefined ? processSubLanguage() : processKeywords();
-               }
-
-               function startNewMode(mode, lexeme) {
-                 var markup = mode.className? buildSpan(mode.className, '', true): '';
-                 if (mode.returnBegin) {
-                   result += markup;
-                   mode_buffer = '';
-                 } else if (mode.excludeBegin) {
-                   result += escape(lexeme) + markup;
-                   mode_buffer = '';
-                 } else {
-                   result += markup;
-                   mode_buffer = lexeme;
-                 }
-                 top = Object.create(mode, {parent: {value: top}});
-               }
-
-               function processLexeme(buffer, lexeme) {
-
-                 mode_buffer += buffer;
-                 if (lexeme === undefined) {
-                   result += processBuffer();
-                   return 0;
-                 }
-
-                 var new_mode = subMode(lexeme, top);
-                 if (new_mode) {
-                   result += processBuffer();
-                   startNewMode(new_mode, lexeme);
-                   return new_mode.returnBegin ? 0 : lexeme.length;
-                 }
-
-                 var end_mode = endOfMode(top, lexeme);
-                 if (end_mode) {
-                   var origin = top;
-                   if (!(origin.returnEnd || origin.excludeEnd)) {
-                     mode_buffer += lexeme;
-                   }
-                   result += processBuffer();
-                   do {
-                     if (top.className) {
-                       result += '</span>';
-                     }
-                     relevance += top.relevance;
-                     top = top.parent;
-                   } while (top != end_mode.parent);
-                   if (origin.excludeEnd) {
-                     result += escape(lexeme);
-                   }
-                   mode_buffer = '';
-                   if (end_mode.starts) {
-                     startNewMode(end_mode.starts, '');
-                   }
-                   return origin.returnEnd ? 0 : lexeme.length;
-                 }
-
-                 if (isIllegal(lexeme, top))
-                   throw new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '<unnamed>') + '"');
-
-                 /*
-                 Parser should not reach this point as all types of lexemes should be caught
-                 earlier, but if it does due to some bug make sure it advances at least one
-                 character forward to prevent infinite looping.
-                 */
-                 mode_buffer += lexeme;
-                 return lexeme.length || 1;
-               }
-
-               var language = getLanguage(name);
-               if (!language) {
-                 throw new Error('Unknown language: "' + name + '"');
-               }
-
-               compileLanguage(language);
-               var top = continuation || language;
-               var result = '';
-               for(var current = top; current != language; current = current.parent) {
-                 if (current.className) {
-                   result += buildSpan(current.className, result, true);
-                 }
-               }
-               var mode_buffer = '';
-               var relevance = 0;
-               try {
-                 var match, count, index = 0;
-                 while (true) {
-                   top.terminators.lastIndex = index;
-                   match = top.terminators.exec(value);
-                   if (!match)
-                     break;
-                   count = processLexeme(value.substr(index, match.index - index), match[0]);
-                   index = match.index + count;
-                 }
-                 processLexeme(value.substr(index));
-                 for(var current = top; current.parent; current = current.parent) { // close dangling modes
-                   if (current.className) {
-                     result += '</span>';
-                   }
-                 };
-                 return {
-                   relevance: relevance,
-                   value: result,
-                   language: name,
-                   top: top
-                 };
-               } catch (e) {
-                 if (e.message.indexOf('Illegal') != -1) {
-                   return {
-                     relevance: 0,
-                     value: escape(value)
-                   };
-                 } else {
-                   throw e;
-                 }
-               }
+         var expanded_contains = [];
+         mode.contains.forEach(function(c) {
+           if (c.variants) {
+             c.variants.forEach(function(v) {expanded_contains.push(inherit(c, v));});
+           } else {
+             expanded_contains.push(c == 'self' ? mode : c);
+           }
+         });
+         mode.contains = expanded_contains;
+         mode.contains.forEach(function(c) {compileMode(c, mode);});
+
+         if (mode.starts) {
+           compileMode(mode.starts, parent);
          }
 
-         /*
-         Highlighting with language detection. Accepts a string with the code to
-         highlight. Returns an object with the following properties:
+         var terminators =
+           mode.contains.map(function(c) {
+             return c.beginKeywords ? '\\.?(' + c.begin + ')\\.?' : c.begin;
+           })
+           .concat([mode.terminator_end, mode.illegal])
+           .map(reStr)
+           .filter(Boolean);
+         mode.terminators = terminators.length ? langRe(terminators.join('|'), true) : {exec: function(s) {return null;}};
+
+         mode.continuation = {};
+       }
+
+       compileMode(language);
+  }
+
+  /*
+  Core highlighting function. Accepts a language name, or an alias, and a
+  string with the code to highlight. Returns an object with the following
+  properties:
+
+  - relevance (int)
+  - value (an HTML string with highlighting markup)
+
+  */
+  function highlight(name, value, ignore_illegals, continuation) {
+
+       function subMode(lexeme, mode) {
+         for (var i = 0; i < mode.contains.length; i++) {
+           if (testRe(mode.contains[i].beginRe, lexeme)) {
+             return mode.contains[i];
+           }
+         }
+       }
 
-         - language (detected language)
-         - relevance (int)
-         - value (an HTML string with highlighting markup)
-         - second_best (object with the same structure for second-best heuristically
-               detected language, may be absent)
+       function endOfMode(mode, lexeme) {
+         if (testRe(mode.endRe, lexeme)) {
+           return mode;
+         }
+         if (mode.endsWithParent) {
+           return endOfMode(mode.parent, lexeme);
+         }
+       }
+
+       function isIllegal(lexeme, mode) {
+         return !ignore_illegals && testRe(mode.illegalRe, lexeme);
+       }
+
+       function keywordMatch(mode, match) {
+         var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0];
+         return mode.keywords.hasOwnProperty(match_str) && mode.keywords[match_str];
+       }
+
+       function buildSpan(classname, insideSpan, leaveOpen, noPrefix) {
+         var classPrefix = noPrefix ? '' : options.classPrefix,
+             openSpan    = '<span class="' + classPrefix,
+             closeSpan   = leaveOpen ? '' : '</span>';
+
+         openSpan += classname + '">';
+
+         return openSpan + insideSpan + closeSpan;
+       }
+
+       function processKeywords() {
+         if (!top.keywords)
+           return escape(mode_buffer);
+         var result = '';
+         var last_index = 0;
+         top.lexemesRe.lastIndex = 0;
+         var match = top.lexemesRe.exec(mode_buffer);
+         while (match) {
+           result += escape(mode_buffer.substr(last_index, match.index - last_index));
+           var keyword_match = keywordMatch(top, match);
+           if (keyword_match) {
+             relevance += keyword_match[1];
+             result += buildSpan(keyword_match[0], escape(match[0]));
+           } else {
+             result += escape(match[0]);
+           }
+           last_index = top.lexemesRe.lastIndex;
+           match = top.lexemesRe.exec(mode_buffer);
+         }
+         return result + escape(mode_buffer.substr(last_index));
+       }
 
-         */
-         function highlightAuto(text, languageSubset) {
-               languageSubset = languageSubset || options.languages || Object.keys(languages);
-               var result = {
-                 relevance: 0,
-                 value: escape(text)
-               };
-               var second_best = result;
-               languageSubset.forEach(function(name) {
-                 if (!getLanguage(name)) {
-                   return;
-                 }
-                 var current = highlight(name, text, false);
-                 current.language = name;
-                 if (current.relevance > second_best.relevance) {
-                   second_best = current;
-                 }
-                 if (current.relevance > result.relevance) {
-                   second_best = result;
-                   result = current;
-                 }
-               });
-               if (second_best.language) {
-                 result.second_best = second_best;
-               }
-               return result;
+       function processSubLanguage() {
+         if (top.subLanguage && !languages[top.subLanguage]) {
+           return escape(mode_buffer);
+         }
+         var result = top.subLanguage ? highlight(top.subLanguage, mode_buffer, true, top.continuation.top) : highlightAuto(mode_buffer);
+         // Counting embedded language score towards the host language may be disabled
+         // with zeroing the containing mode relevance. Usecase in point is Markdown that
+         // allows XML everywhere and makes every XML snippet to have a much larger Markdown
+         // score.
+         if (top.relevance > 0) {
+           relevance += result.relevance;
+         }
+         if (top.subLanguageMode == 'continuous') {
+           top.continuation.top = result.top;
          }
+         return buildSpan(result.language, result.value, false, true);
+       }
+
+       function processBuffer() {
+         return top.subLanguage !== undefined ? processSubLanguage() : processKeywords();
+       }
+
+       function startNewMode(mode, lexeme) {
+         var markup = mode.className? buildSpan(mode.className, '', true): '';
+         if (mode.returnBegin) {
+           result += markup;
+           mode_buffer = '';
+         } else if (mode.excludeBegin) {
+           result += escape(lexeme) + markup;
+           mode_buffer = '';
+         } else {
+           result += markup;
+           mode_buffer = lexeme;
+         }
+         top = Object.create(mode, {parent: {value: top}});
+       }
 
-         /*
-         Post-processing of the highlighted markup:
+       function processLexeme(buffer, lexeme) {
 
-         - replace TABs with something more useful
-         - replace real line-breaks with '<br>' for non-pre containers
+         mode_buffer += buffer;
+         if (lexeme === undefined) {
+           result += processBuffer();
+           return 0;
+         }
 
-         */
-         function fixMarkup(value) {
-               if (options.tabReplace) {
-                 value = value.replace(/^((<[^>]+>|\t)+)/gm, function(match, p1, offset, s) {
-                   return p1.replace(/\t/g, options.tabReplace);
-                 });
-               }
-               if (options.useBR) {
-                 value = value.replace(/\n/g, '<br>');
-               }
-               return value;
+         var new_mode = subMode(lexeme, top);
+         if (new_mode) {
+           result += processBuffer();
+           startNewMode(new_mode, lexeme);
+           return new_mode.returnBegin ? 0 : lexeme.length;
          }
 
-         /*
-         Applies highlighting to a DOM node containing code. Accepts a DOM node and
-         two optional parameters for fixMarkup.
-         */
-         function highlightBlock(block) {
-               var text = options.useBR ? block.innerHTML
-                 .replace(/\n/g,'').replace(/<br>|<br [^>]*>/g, '\n').replace(/<[^>]*>/g,'')
-                 : block.textContent;
-               var language = blockLanguage(block);
-               if (language == 'no-highlight')
-                   return;
-               var result = language ? highlight(language, text, true) : highlightAuto(text);
-               var original = nodeStream(block);
-               if (original.length) {
-                 var pre = document.createElementNS('http://www.w3.org/1999/xhtml', 'pre');
-                 pre.innerHTML = result.value;
-                 result.value = mergeStreams(original, nodeStream(pre), text);
-               }
-               result.value = fixMarkup(result.value);
-
-               block.innerHTML = result.value;
-               block.className += ' hljs ' + (!language && result.language || '');
-               block.result = {
-                 language: result.language,
-                 re: result.relevance
-               };
-               if (result.second_best) {
-                 block.second_best = {
-                   language: result.second_best.language,
-                   re: result.second_best.relevance
-                 };
-               }
+         var end_mode = endOfMode(top, lexeme);
+         if (end_mode) {
+           var origin = top;
+           if (!(origin.returnEnd || origin.excludeEnd)) {
+             mode_buffer += lexeme;
+           }
+           result += processBuffer();
+           do {
+             if (top.className) {
+               result += '</span>';
+             }
+             relevance += top.relevance;
+             top = top.parent;
+           } while (top != end_mode.parent);
+           if (origin.excludeEnd) {
+             result += escape(lexeme);
+           }
+           mode_buffer = '';
+           if (end_mode.starts) {
+             startNewMode(end_mode.starts, '');
+           }
+           return origin.returnEnd ? 0 : lexeme.length;
          }
 
-         var options = {
-               classPrefix: 'hljs-',
-               tabReplace: null,
-               useBR: false,
-               languages: undefined
-         };
+         if (isIllegal(lexeme, top))
+           throw new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '<unnamed>') + '"');
 
          /*
-         Updates highlight.js global options with values passed in the form of an object
+         Parser should not reach this point as all types of lexemes should be caught
+         earlier, but if it does due to some bug make sure it advances at least one
+         character forward to prevent infinite looping.
          */
-         function configure(user_options) {
-               options = inherit(options, user_options);
+         mode_buffer += lexeme;
+         return lexeme.length || 1;
+       }
+
+       var language = getLanguage(name);
+       if (!language) {
+         throw new Error('Unknown language: "' + name + '"');
+       }
+
+       compileLanguage(language);
+       var top = continuation || language;
+       var result = '';
+       for(var current = top; current != language; current = current.parent) {
+         if (current.className) {
+           result += buildSpan(current.className, result, true);
          }
-
-         /*
-         Applies highlighting to all <pre><code>..</code></pre> blocks on a page.
-         */
-         function initHighlighting() {
-               if (initHighlighting.called)
-                 return;
-               initHighlighting.called = true;
-
-               var blocks = document.querySelectorAll('pre code');
-               Array.prototype.forEach.call(blocks, highlightBlock);
+       }
+       var mode_buffer = '';
+       var relevance = 0;
+       try {
+         var match, count, index = 0;
+         while (true) {
+           top.terminators.lastIndex = index;
+           match = top.terminators.exec(value);
+           if (!match)
+             break;
+           count = processLexeme(value.substr(index, match.index - index), match[0]);
+           index = match.index + count;
          }
-
-         /*
-         Attaches highlighting to the page load event.
-         */
-         function initHighlightingOnLoad() {
-               addEventListener('DOMContentLoaded', initHighlighting, false);
-               addEventListener('load', initHighlighting, false);
+         processLexeme(value.substr(index));
+         for(var current = top; current.parent; current = current.parent) { // close dangling modes
+           if (current.className) {
+             result += '</span>';
+           }
+         };
+         return {
+           relevance: relevance,
+           value: result,
+           language: name,
+           top: top
+         };
+       } catch (e) {
+         if (e.message.indexOf('Illegal') != -1) {
+           return {
+             relevance: 0,
+             value: escape(value)
+           };
+         } else {
+           throw e;
          }
-
-         var languages = {};
-         var aliases = {};
-
-         function registerLanguage(name, language) {
-               var lang = languages[name] = language(this);
-               if (lang.aliases) {
-                 lang.aliases.forEach(function(alias) {aliases[alias] = name;});
-               }
+       }
+  }
+
+  /*
+  Highlighting with language detection. Accepts a string with the code to
+  highlight. Returns an object with the following properties:
+
+  - language (detected language)
+  - relevance (int)
+  - value (an HTML string with highlighting markup)
+  - second_best (object with the same structure for second-best heuristically
+       detected language, may be absent)
+
+  */
+  function highlightAuto(text, languageSubset) {
+       languageSubset = languageSubset || options.languages || Object.keys(languages);
+       var result = {
+         relevance: 0,
+         value: escape(text)
+       };
+       var second_best = result;
+       languageSubset.forEach(function(name) {
+         if (!getLanguage(name)) {
+           return;
          }
-
-         function listLanguages() {
-               return Object.keys(languages);
+         var current = highlight(name, text, false);
+         current.language = name;
+         if (current.relevance > second_best.relevance) {
+           second_best = current;
          }
-
-         function getLanguage(name) {
-               return languages[name] || languages[aliases[name]];
+         if (current.relevance > result.relevance) {
+           second_best = result;
+           result = current;
          }
-
-         /* Interface definition */
-
-         this.highlight = highlight;
-         this.highlightAuto = highlightAuto;
-         this.fixMarkup = fixMarkup;
-         this.highlightBlock = highlightBlock;
-         this.configure = configure;
-         this.initHighlighting = initHighlighting;
-         this.initHighlightingOnLoad = initHighlightingOnLoad;
-         this.registerLanguage = registerLanguage;
-         this.listLanguages = listLanguages;
-         this.getLanguage = getLanguage;
-         this.inherit = inherit;
-
-         // Common regexps
-         this.IDENT_RE = '[a-zA-Z][a-zA-Z0-9_]*';
-         this.UNDERSCORE_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*';
-         this.NUMBER_RE = '\\b\\d+(\\.\\d+)?';
-         this.C_NUMBER_RE = '(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float
-         this.BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b...
-         this.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~';
-
-         // Common modes
-         this.BACKSLASH_ESCAPE = {
-               begin: '\\\\[\\s\\S]', relevance: 0
-         };
-         this.APOS_STRING_MODE = {
-               className: 'string',
-               begin: '\'', end: '\'',
-               illegal: '\\n',
-               contains: [this.BACKSLASH_ESCAPE]
-         };
-         this.QUOTE_STRING_MODE = {
-               className: 'string',
-               begin: '"', end: '"',
-               illegal: '\\n',
-               contains: [this.BACKSLASH_ESCAPE]
-         };
-         this.PHRASAL_WORDS_MODE = {
-               begin: /\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/
-         };
-         this.C_LINE_COMMENT_MODE = {
-               className: 'comment',
-               begin: '//', end: '$',
-               contains: [this.PHRASAL_WORDS_MODE]
-         };
-         this.C_BLOCK_COMMENT_MODE = {
-               className: 'comment',
-               begin: '/\\*', end: '\\*/',
-               contains: [this.PHRASAL_WORDS_MODE]
-         };
-         this.HASH_COMMENT_MODE = {
-               className: 'comment',
-               begin: '#', end: '$',
-               contains: [this.PHRASAL_WORDS_MODE]
-         };
-         this.NUMBER_MODE = {
-               className: 'number',
-               begin: this.NUMBER_RE,
-               relevance: 0
-         };
-         this.C_NUMBER_MODE = {
-               className: 'number',
-               begin: this.C_NUMBER_RE,
-               relevance: 0
-         };
-         this.BINARY_NUMBER_MODE = {
-               className: 'number',
-               begin: this.BINARY_NUMBER_RE,
-               relevance: 0
-         };
-         this.CSS_NUMBER_MODE = {
-               className: 'number',
-               begin: this.NUMBER_RE + '(' +
-                 '%|em|ex|ch|rem'  +
-                 '|vw|vh|vmin|vmax' +
-                 '|cm|mm|in|pt|pc|px' +
-                 '|deg|grad|rad|turn' +
-                 '|s|ms' +
-                 '|Hz|kHz' +
-                 '|dpi|dpcm|dppx' +
-                 ')?',
-               relevance: 0
-         };
-         this.REGEXP_MODE = {
-               className: 'regexp',
-               begin: /\//, end: /\/[gim]*/,
-               illegal: /\n/,
-               contains: [
-                 this.BACKSLASH_ESCAPE,
-                 {
-                   begin: /\[/, end: /\]/,
-                   relevance: 0,
-                   contains: [this.BACKSLASH_ESCAPE]
-                 }
-               ]
-         };
-         this.TITLE_MODE = {
-               className: 'title',
-               begin: this.IDENT_RE,
-               relevance: 0
-         };
-         this.UNDERSCORE_TITLE_MODE = {
-               className: 'title',
-               begin: this.UNDERSCORE_IDENT_RE,
-               relevance: 0
-         };
+       });
+       if (second_best.language) {
+         result.second_best = second_best;
+       }
+       return result;
+  }
+
+  /*
+  Post-processing of the highlighted markup:
+
+  - replace TABs with something more useful
+  - replace real line-breaks with '<br>' for non-pre containers
+
+  */
+  function fixMarkup(value) {
+       if (options.tabReplace) {
+         value = value.replace(/^((<[^>]+>|\t)+)/gm, function(match, p1, offset, s) {
+           return p1.replace(/\t/g, options.tabReplace);
+         });
+       }
+       if (options.useBR) {
+         value = value.replace(/\n/g, '<br>');
+       }
+       return value;
+  }
+
+  /*
+  Applies highlighting to a DOM node containing code. Accepts a DOM node and
+  two optional parameters for fixMarkup.
+  */
+  function highlightBlock(block) {
+       var text = options.useBR ? block.innerHTML
+         .replace(/\n/g,'').replace(/<br>|<br [^>]*>/g, '\n').replace(/<[^>]*>/g,'')
+         : block.textContent;
+       var language = blockLanguage(block);
+       if (language == 'no-highlight')
+           return;
+       var result = language ? highlight(language, text, true) : highlightAuto(text);
+       var original = nodeStream(block);
+       if (original.length) {
+         var pre = document.createElementNS('http://www.w3.org/1999/xhtml', 'pre');
+         pre.innerHTML = result.value;
+         result.value = mergeStreams(original, nodeStream(pre), text);
+       }
+       result.value = fixMarkup(result.value);
+
+       block.innerHTML = result.value;
+       block.className += ' hljs ' + (!language && result.language || '');
+       block.result = {
+         language: result.language,
+         re: result.relevance
        };
-       return hljs;
-});
-
+       if (result.second_best) {
+         block.second_best = {
+           language: result.second_best.language,
+           re: result.second_best.relevance
+         };
+       }
+  }
+
+  var options = {
+       classPrefix: 'hljs-',
+       tabReplace: null,
+       useBR: false,
+       languages: undefined
+  };
+
+  /*
+  Updates highlight.js global options with values passed in the form of an object
+  */
+  function configure(user_options) {
+       options = inherit(options, user_options);
+  }
+
+  /*
+  Applies highlighting to all <pre><code>..</code></pre> blocks on a page.
+  */
+  function initHighlighting() {
+       if (initHighlighting.called)
+         return;
+       initHighlighting.called = true;
+
+       var blocks = document.querySelectorAll('pre code');
+       Array.prototype.forEach.call(blocks, highlightBlock);
+  }
+
+  /*
+  Attaches highlighting to the page load event.
+  */
+  function initHighlightingOnLoad() {
+       addEventListener('DOMContentLoaded', initHighlighting, false);
+       addEventListener('load', initHighlighting, false);
+  }
+
+  var languages = {};
+  var aliases = {};
+
+  function registerLanguage(name, language) {
+       var lang = languages[name] = language(this);
+       if (lang.aliases) {
+         lang.aliases.forEach(function(alias) {aliases[alias] = name;});
+       }
+  }
+
+  function listLanguages() {
+       return Object.keys(languages);
+  }
+
+  function getLanguage(name) {
+       return languages[name] || languages[aliases[name]];
+  }
+
+  /* Interface definition */
+
+  this.highlight = highlight;
+  this.highlightAuto = highlightAuto;
+  this.fixMarkup = fixMarkup;
+  this.highlightBlock = highlightBlock;
+  this.configure = configure;
+  this.initHighlighting = initHighlighting;
+  this.initHighlightingOnLoad = initHighlightingOnLoad;
+  this.registerLanguage = registerLanguage;
+  this.listLanguages = listLanguages;
+  this.getLanguage = getLanguage;
+  this.inherit = inherit;
+
+  // Common regexps
+  this.IDENT_RE = '[a-zA-Z][a-zA-Z0-9_]*';
+  this.UNDERSCORE_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*';
+  this.NUMBER_RE = '\\b\\d+(\\.\\d+)?';
+  this.C_NUMBER_RE = '(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float
+  this.BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b...
+  this.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~';
+
+  // Common modes
+  this.BACKSLASH_ESCAPE = {
+       begin: '\\\\[\\s\\S]', relevance: 0
+  };
+  this.APOS_STRING_MODE = {
+       className: 'string',
+       begin: '\'', end: '\'',
+       illegal: '\\n',
+       contains: [this.BACKSLASH_ESCAPE]
+  };
+  this.QUOTE_STRING_MODE = {
+       className: 'string',
+       begin: '"', end: '"',
+       illegal: '\\n',
+       contains: [this.BACKSLASH_ESCAPE]
+  };
+  this.PHRASAL_WORDS_MODE = {
+       begin: /\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/
+  };
+  this.C_LINE_COMMENT_MODE = {
+       className: 'comment',
+       begin: '//', end: '$',
+       contains: [this.PHRASAL_WORDS_MODE]
+  };
+  this.C_BLOCK_COMMENT_MODE = {
+       className: 'comment',
+       begin: '/\\*', end: '\\*/',
+       contains: [this.PHRASAL_WORDS_MODE]
+  };
+  this.HASH_COMMENT_MODE = {
+       className: 'comment',
+       begin: '#', end: '$',
+       contains: [this.PHRASAL_WORDS_MODE]
+  };
+  this.NUMBER_MODE = {
+       className: 'number',
+       begin: this.NUMBER_RE,
+       relevance: 0
+  };
+  this.C_NUMBER_MODE = {
+       className: 'number',
+       begin: this.C_NUMBER_RE,
+       relevance: 0
+  };
+  this.BINARY_NUMBER_MODE = {
+       className: 'number',
+       begin: this.BINARY_NUMBER_RE,
+       relevance: 0
+  };
+  this.CSS_NUMBER_MODE = {
+       className: 'number',
+       begin: this.NUMBER_RE + '(' +
+         '%|em|ex|ch|rem'  +
+         '|vw|vh|vmin|vmax' +
+         '|cm|mm|in|pt|pc|px' +
+         '|deg|grad|rad|turn' +
+         '|s|ms' +
+         '|Hz|kHz' +
+         '|dpi|dpcm|dppx' +
+         ')?',
+       relevance: 0
+  };
+  this.REGEXP_MODE = {
+       className: 'regexp',
+       begin: /\//, end: /\/[gim]*/,
+       illegal: /\n/,
+       contains: [
+         this.BACKSLASH_ESCAPE,
+         {
+           begin: /\[/, end: /\]/,
+           relevance: 0,
+           contains: [this.BACKSLASH_ESCAPE]
+         }
+       ]
+  };
+  this.TITLE_MODE = {
+       className: 'title',
+       begin: this.IDENT_RE,
+       relevance: 0
+  };
+  this.UNDERSCORE_TITLE_MODE = {
+       className: 'title',
+       begin: this.UNDERSCORE_IDENT_RE,
+       relevance: 0
+  };
+};