diff options
Diffstat (limited to 'tools/eslint/node_modules/remark-parse/lib/parser.js')
-rw-r--r-- | tools/eslint/node_modules/remark-parse/lib/parser.js | 6318 |
1 files changed, 6318 insertions, 0 deletions
diff --git a/tools/eslint/node_modules/remark-parse/lib/parser.js b/tools/eslint/node_modules/remark-parse/lib/parser.js new file mode 100644 index 00000000000..f55f41c95e4 --- /dev/null +++ b/tools/eslint/node_modules/remark-parse/lib/parser.js @@ -0,0 +1,6318 @@ +/** + * @author Titus Wormer + * @copyright 2015-2016 Titus Wormer + * @license MIT + * @module remark:parser + * @fileoverview Markdown parser. + */ + +'use strict'; + +/* eslint-env commonjs */ + +/* + * Dependencies. + */ + +var decode = require('parse-entities'); +var repeat = require('repeat-string'); +var trim = require('trim'); +var trimTrailingLines = require('trim-trailing-lines'); +var extend = require('extend'); +var vfileLocation = require('vfile-location'); +var removePosition = require('unist-util-remove-position'); +var collapseWhiteSpace = require('collapse-white-space'); +var defaultOptions = require('./defaults.js'); +var escapes = require('./escapes.json'); +var blockElements = require('./block-elements.json'); + +/* + * Methods. + */ + +var has = {}.hasOwnProperty; + +/* + * Numeric constants. + */ + +var SPACE_SIZE = 1; +var TAB_SIZE = 4; +var CODE_INDENT_LENGTH = 4; +var MIN_FENCE_COUNT = 3; +var MAX_ATX_COUNT = 6; +var MAX_LINE_HEADING_INDENT = 3; +var THEMATIC_BREAK_MARKER_COUNT = 3; +var MIN_CLOSING_HTML_NEWLINE_COUNT = 2; +var MIN_BREAK_LENGTH = 2; +var MIN_TABLE_COLUMNS = 2; +var MIN_TABLE_ROWS = 2; + +/* + * Error messages. + */ + +var ERR_INFINITE_LOOP = 'Infinite loop'; +var ERR_MISSING_LOCATOR = 'Missing locator: '; +var ERR_INCORRECTLY_EATEN = 'Incorrectly eaten value: please report this ' + + 'warning on http://git.io/vg5Ft'; + +/* + * Expressions. + */ + +var EXPRESSION_BULLET = /^([ \t]*)([*+-]|\d+[.)])( {1,4}(?! )| |\t|$|(?=\n))([^\n]*)/; +var EXPRESSION_PEDANTIC_BULLET = /^([ \t]*)([*+-]|\d+[.)])([ \t]+)/; +var EXPRESSION_INITIAL_INDENT = /^( {1,4}|\t)?/gm; +var EXPRESSION_INITIAL_TAB = /^( {4}|\t)?/gm; +var EXPRESSION_HTML_LINK_OPEN = /^<a /i; +var EXPRESSION_HTML_LINK_CLOSE = /^<\/a>/i; +var EXPRESSION_LOOSE_LIST_ITEM = /\n\n(?!\s*$)/; +var EXPRESSION_TASK_ITEM = /^\[([\ \t]|x|X)\][\ \t]/; +var EXPRESSION_LINE_BREAKS = /\r\n|\r/g; + +/* + * Characters. + */ + +var C_BACKSLASH = '\\'; +var C_UNDERSCORE = '_'; +var C_ASTERISK = '*'; +var C_TICK = '`'; +var C_AT_SIGN = '@'; +var C_HASH = '#'; +var C_PLUS = '+'; +var C_DASH = '-'; +var C_DOT = '.'; +var C_PIPE = '|'; +var C_DOUBLE_QUOTE = '"'; +var C_SINGLE_QUOTE = '\''; +var C_COMMA = ','; +var C_SLASH = '/'; +var C_COLON = ':'; +var C_SEMI_COLON = ';'; +var C_QUESTION_MARK = '?'; +var C_CARET = '^'; +var C_EQUALS = '='; +var C_EXCLAMATION_MARK = '!'; +var C_TILDE = '~'; +var C_LT = '<'; +var C_GT = '>'; +var C_BRACKET_OPEN = '['; +var C_BRACKET_CLOSE = ']'; +var C_PAREN_OPEN = '('; +var C_PAREN_CLOSE = ')'; +var C_SPACE = ' '; +var C_FORM_FEED = '\f'; +var C_NEWLINE = '\n'; +var C_CARRIAGE_RETURN = '\r'; +var C_TAB = '\t'; +var C_VERTICAL_TAB = '\v'; +var C_NO_BREAK_SPACE = '\u00a0'; +var C_OGHAM_SPACE = '\u1680'; +var C_MONGOLIAN_VOWEL_SEPARATOR = '\u180e'; +var C_EN_QUAD = '\u2000'; +var C_EM_QUAD = '\u2001'; +var C_EN_SPACE = '\u2002'; +var C_EM_SPACE = '\u2003'; +var C_THREE_PER_EM_SPACE = '\u2004'; +var C_FOUR_PER_EM_SPACE = '\u2005'; +var C_SIX_PER_EM_SPACE = '\u2006'; +var C_FIGURE_SPACE = '\u2007'; +var C_PUNCTUATION_SPACE = '\u2008'; +var C_THIN_SPACE = '\u2009'; +var C_HAIR_SPACE = '\u200a'; +var C_LINE_SEPARATOR = '\u2028'; +var C_PARAGRAPH_SEPARATOR = '\u2029'; +var C_NARROW_NO_BREAK_SPACE = '\u202f'; +var C_IDEOGRAPHIC_SPACE = '\u3000'; +var C_ZERO_WIDTH_NO_BREAK_SPACE = '\ufeff'; +var C_X_LOWER = 'x'; + +/* + * Character codes. + */ + +var CC_A_LOWER = 'a'.charCodeAt(0); +var CC_A_UPPER = 'A'.charCodeAt(0); +var CC_Z_LOWER = 'z'.charCodeAt(0); +var CC_Z_UPPER = 'Z'.charCodeAt(0); +var CC_0 = '0'.charCodeAt(0); +var CC_9 = '9'.charCodeAt(0); + +/* + * Protocols. + */ + +var HTTP_PROTOCOL = 'http://'; +var HTTPS_PROTOCOL = 'https://'; +var MAILTO_PROTOCOL = 'mailto:'; + +var PROTOCOLS = [ + HTTP_PROTOCOL, + HTTPS_PROTOCOL, + MAILTO_PROTOCOL +]; + +var PROTOCOLS_LENGTH = PROTOCOLS.length; + +/* + * Textual constants. + */ + +var YAML_FENCE = repeat(C_DASH, 3); +var CODE_INDENT = repeat(C_SPACE, CODE_INDENT_LENGTH); +var EMPTY = ''; +var BLOCK = 'block'; +var INLINE = 'inline'; +var COMMENT_START = '<!--'; +var COMMENT_END = '-->'; +var CDATA_START = '<![CDATA['; +var CDATA_END = ']]>'; +var COMMENT_END_CHAR = COMMENT_END.charAt(0); +var CDATA_END_CHAR = CDATA_END.charAt(0); +var COMMENT_START_LENGTH = COMMENT_START.length; +var COMMENT_END_LENGTH = COMMENT_END.length; +var CDATA_START_LENGTH = CDATA_START.length; +var CDATA_END_LENGTH = CDATA_END.length; + +/* + * Node types. + */ + +var T_THEMATIC_BREAK = 'thematicBreak'; +var T_HTML = 'html'; +var T_YAML = 'yaml'; +var T_TABLE = 'table'; +var T_TABLE_CELL = 'tableCell'; +var T_TABLE_HEADER = 'tableRow'; +var T_TABLE_ROW = 'tableRow'; +var T_PARAGRAPH = 'paragraph'; +var T_TEXT = 'text'; +var T_CODE = 'code'; +var T_LIST = 'list'; +var T_LIST_ITEM = 'listItem'; +var T_DEFINITION = 'definition'; +var T_FOOTNOTE_DEFINITION = 'footnoteDefinition'; +var T_HEADING = 'heading'; +var T_BLOCKQUOTE = 'blockquote'; +var T_LINK = 'link'; +var T_IMAGE = 'image'; +var T_FOOTNOTE = 'footnote'; +var T_STRONG = 'strong'; +var T_EMPHASIS = 'emphasis'; +var T_DELETE = 'delete'; +var T_INLINE_CODE = 'inlineCode'; +var T_BREAK = 'break'; +var T_ROOT = 'root'; + +/* + * Available table alignments. + */ + +var TABLE_ALIGN_LEFT = 'left'; +var TABLE_ALIGN_CENTER = 'center'; +var TABLE_ALIGN_RIGHT = 'right'; +var TABLE_ALIGN_NONE = null; + +/* + * Available reference types. + */ + +var REFERENCE_TYPE_SHORTCUT = 'shortcut'; +var REFERENCE_TYPE_COLLAPSED = 'collapsed'; +var REFERENCE_TYPE_FULL = 'full'; + +/* + * A map of characters, and their column length, + * which can be used as indentation. + */ + +var INDENTATION_CHARACTERS = {}; + +INDENTATION_CHARACTERS[C_SPACE] = SPACE_SIZE; +INDENTATION_CHARACTERS[C_TAB] = TAB_SIZE; + +/* + * A map of characters, which can be used to mark emphasis. + */ + +var EMPHASIS_MARKERS = {}; + +EMPHASIS_MARKERS[C_ASTERISK] = true; +EMPHASIS_MARKERS[C_UNDERSCORE] = true; + +/* + * A map of characters, which can be used to mark rules. + */ + +var RULE_MARKERS = {}; + +RULE_MARKERS[C_ASTERISK] = true; +RULE_MARKERS[C_UNDERSCORE] = true; +RULE_MARKERS[C_DASH] = true; + +/* + * A map of characters which can be used to mark + * list-items. + */ + +var LIST_UNORDERED_MARKERS = {}; + +LIST_UNORDERED_MARKERS[C_ASTERISK] = true; +LIST_UNORDERED_MARKERS[C_PLUS] = true; +LIST_UNORDERED_MARKERS[C_DASH] = true; + +/* + * A map of characters which can be used to mark + * list-items after a digit. + */ + +var LIST_ORDERED_MARKERS = {}; + +LIST_ORDERED_MARKERS[C_DOT] = true; + +/* + * A map of characters which can be used to mark + * list-items after a digit. + */ + +var LIST_ORDERED_COMMONMARK_MARKERS = {}; + +LIST_ORDERED_COMMONMARK_MARKERS[C_DOT] = true; +LIST_ORDERED_COMMONMARK_MARKERS[C_PAREN_CLOSE] = true; + +/* + * A map of characters, which can be used to mark link + * and image titles. + */ + +var LINK_MARKERS = {}; + +LINK_MARKERS[C_DOUBLE_QUOTE] = C_DOUBLE_QUOTE; +LINK_MARKERS[C_SINGLE_QUOTE] = C_SINGLE_QUOTE; + +/* + * A map of characters, which can be used to mark link + * and image titles in commonmark-mode. + */ + +var COMMONMARK_LINK_MARKERS = {}; + +COMMONMARK_LINK_MARKERS[C_DOUBLE_QUOTE] = C_DOUBLE_QUOTE; +COMMONMARK_LINK_MARKERS[C_SINGLE_QUOTE] = C_SINGLE_QUOTE; +COMMONMARK_LINK_MARKERS[C_PAREN_OPEN] = C_PAREN_CLOSE; + +/* + * A map of characters which can be used to mark setext + * headers, mapping to their corresponding depth. + */ + +var SETEXT_MARKERS = {}; + +SETEXT_MARKERS[C_EQUALS] = 1; +SETEXT_MARKERS[C_DASH] = 2; + +/* + * A map of two functions which can create list items. + */ + +var LIST_ITEM_MAP = {}; + +LIST_ITEM_MAP.true = renderPedanticListItem; +LIST_ITEM_MAP.false = renderNormalListItem; + +/** + * Check whether `character` is alphabetic. + * + * @param {string} character - Single character to check. + * @return {boolean} - Whether `character` is alphabetic. + */ +function isAlphabetic(character) { + var code = character.charCodeAt(0); + + return (code >= CC_A_LOWER && code <= CC_Z_LOWER) || + (code >= CC_A_UPPER && code <= CC_Z_UPPER); +} + +/** + * Check whether `character` is numeric. + * + * @param {string} character - Single character to check. + * @return {boolean} - Whether `character` is numeric. + */ +function isNumeric(character) { + var code = character.charCodeAt(0); + + return code >= CC_0 && code <= CC_9; +} + +/** + * Check whether `character` is a word character. + * + * @param {string} character - Single character to check. + * @return {boolean} - Whether `character` is a word + * character. + */ +function isWordCharacter(character) { + return character === C_UNDERSCORE || + isAlphabetic(character) || + isNumeric(character); +} + +/** + * Check whether `character` is white-space. + * + * @param {string} character - Single character to check. + * @return {boolean} - Whether `character` is white-space. + */ +function isWhiteSpace(character) { + return character === C_SPACE || + character === C_FORM_FEED || + character === C_NEWLINE || + character === C_CARRIAGE_RETURN || + character === C_TAB || + character === C_VERTICAL_TAB || + character === C_NO_BREAK_SPACE || + character === C_OGHAM_SPACE || + character === C_MONGOLIAN_VOWEL_SEPARATOR || + character === C_EN_QUAD || + character === C_EM_QUAD || + character === C_EN_SPACE || + character === C_EM_SPACE || + character === C_THREE_PER_EM_SPACE || + character === C_FOUR_PER_EM_SPACE || + character === C_SIX_PER_EM_SPACE || + character === C_FIGURE_SPACE || + character === C_PUNCTUATION_SPACE || + character === C_THIN_SPACE || + character === C_HAIR_SPACE || + character === C_LINE_SEPARATOR || + character === C_PARAGRAPH_SEPARATOR || + character === C_NARROW_NO_BREAK_SPACE || + character === C_IDEOGRAPHIC_SPACE || + character === C_ZERO_WIDTH_NO_BREAK_SPACE; +} + +/** + * Check whether `character` can be inside an unquoted + * attribute value. + * + * @param {string} character - Single character to check. + * @return {boolean} - Whether `character` can be inside + * an unquoted attribute value. + */ +function isUnquotedAttributeCharacter(character) { + return character !== C_DOUBLE_QUOTE && + character !== C_SINGLE_QUOTE && + character !== C_EQUALS && + character !== C_LT && + character !== C_GT && + character !== C_TICK; +} + +/** + * Check whether `character` can be inside a double-quoted + * attribute value. + * + * @property {string} delimiter - Closing delimiter. + * @param {string} character - Single character to check. + * @return {boolean} - Whether `character` can be inside + * a double-quoted attribute value. + */ +function isDoubleQuotedAttributeCharacter(character) { + return character !== C_DOUBLE_QUOTE; +} + +isDoubleQuotedAttributeCharacter.delimiter = C_DOUBLE_QUOTE; + +/** + * Check whether `character` can be inside a single-quoted + * attribute value. + * + * @property {string} delimiter - Closing delimiter. + * @param {string} character - Single character to check. + * @return {boolean} - Whether `character` can be inside + * a single-quoted attribute value. + */ +function isSingleQuotedAttributeCharacter(character) { + return character !== C_SINGLE_QUOTE; +} + +isSingleQuotedAttributeCharacter.delimiter = C_SINGLE_QUOTE; + +/** + * Check whether `character` can be inside an enclosed + * URI. + * + * @property {string} delimiter - Closing delimiter. + * @param {string} character - Character to test. + * @return {boolean} - Whether `character` can be inside + * an enclosed URI. + */ +function isEnclosedURLCharacter(character) { + return character !== C_GT && + character !== C_BRACKET_OPEN && + character !== C_BRACKET_CLOSE; +} + +isEnclosedURLCharacter.delimiter = C_GT; + +/** + * Check whether `character` can be inside an unclosed + * URI. + * + * @param {string} character - Character to test. + * @return {boolean} - Whether `character` can be inside + * an unclosed URI. + */ +function isUnclosedURLCharacter(character) { + return character !== C_BRACKET_OPEN && + character !== C_BRACKET_CLOSE && + !isWhiteSpace(character); +} + +/** + * Normalize an identifier. Collapses multiple white space + * characters into a single space, and removes casing. + * + * @example + * normalizeIdentifier('FOO\t bar'); // 'foo bar' + * + * @param {string} value - Content to normalize. + * @return {string} - Normalized content. + */ +function normalize(value) { + return collapseWhiteSpace(value).toLowerCase(); +} + +/** + * Construct a state `toggler`: a function which inverses + * `property` in context based on its current value. + * The by `toggler` returned function restores that value. + * + * @example + * var context = {}; + * var key = 'foo'; + * var val = true; + * context[key] = val; + * context.enter = toggle(key, val); + * context[key]; // true + * var exit = context.enter(); + * context[key]; // false + * var nested = context.enter(); + * context[key]; // false + * nested(); + * context[key]; // false + * exit(); + * context[key]; // true + * + * @param {string} key - Property to toggle. + * @param {boolean} state - It's default state. + * @return {function(): function()} - Enter. + */ +function toggle(key, state) { + /** + * Construct a toggler for the bound `key`. + * + * @return {Function} - Exit state. + */ + function enter() { + var self = this; + var current = self[key]; + + self[key] = !state; + + /** + * State canceler, cancels the state, if allowed. + */ + function exit() { + self[key] = current; + } + + return exit; + } + + return enter; +} + +/* + * Define nodes of a type which can be merged. + */ + +var MERGEABLE_NODES = {}; + +/** + * Check whether a node is mergeable with adjacent nodes. + * + * @param {Object} node - Node to check. + * @return {boolean} - Whether `node` is mergable. + */ +function mergeable(node) { + var start; + var end; + + if (node.type !== 'text' || !node.position) { + return true; + } + + start = node.position.start; + end = node.position.end; + + /* + * Only merge nodes which occupy the same size as their + * `value`. + */ + + return start.line !== end.line || + end.column - start.column === node.value.length; +} + +/** + * Merge two text nodes: `node` into `prev`. + * + * @param {Object} prev - Preceding sibling. + * @param {Object} node - Following sibling. + * @return {Object} - `prev`. + */ +MERGEABLE_NODES.text = function (prev, node) { + prev.value += node.value; + + return prev; +}; + +/** + * Merge two blockquotes: `node` into `prev`, unless in + * CommonMark mode. + * + * @param {Object} prev - Preceding sibling. + * @param {Object} node - Following sibling. + * @return {Object} - `prev`, or `node` in CommonMark mode. + */ +MERGEABLE_NODES.blockquote = function (prev, node) { + if (this.options.commonmark) { + return node; + } + + prev.children = prev.children.concat(node.children); + + return prev; +}; + +/** + * Factory to create an entity decoder. + * + * @param {Object} context - Context to attach to, e.g., + * a parser. + * @return {Function} - See `decode`. + */ +function decodeFactory(context) { + /** + * Normalize `position` to add an `indent`. + * + * @param {Position} position - Reference + * @return {Position} - Augmented with `indent`. + */ + function normalize(position) { + return { + 'start': position, + 'indent': context.getIndent(position.line) + }; + } + + /** + * Handle a warning. + * + * @this {VFile} - Virtual file. + * @param {string} reason - Reason for warning. + * @param {Position} position - Place of warning. + * @param {number} code - Code for warning. + */ + function handleWarning(reason, position, code) { + if (code === 3) { + return; + } + + context.file.warn(reason, position); + } + + /** + * Decode `value` (at `position`) into text-nodes. + * + * @param {string} value - Value to parse. + * @param {Position} position - Position to start parsing at. + * @param {Function} handler - Node handler. + */ + function decoder(value, position, handler) { + decode(value, { + 'position': normalize(position), + 'warning': handleWarning, + 'text': handler, + 'reference': handler, + 'textContext': context, + 'referenceContext': context + }); + } + + /** + * Decode `value` (at `position`) into a string. + * + * @param {string} value - Value to parse. + * @param {Position} position - Position to start + * parsing at. + * @return {string} - Plain-text. + */ + function decodeRaw(value, position) { + return decode(value, { + 'position': normalize(position), + 'warning': handleWarning + }); + } + + decoder.raw = decodeRaw; + + return decoder; +} + +/** + * Factory to de-escape a value, based on a list at `key` + * in `scope`. + * + * @example + * var scope = {escape: ['a']} + * var descape = descapeFactory(scope, 'escape'); + * + * @param {Object} scope - List of escapable characters. + * @param {string} key - Key in `map` at which the list + * exists. + * @return {function(string): string} - Function which + * takes a value and returns its unescaped version. + */ +function descapeFactory(scope, key) { + /** + * De-escape a string using the expression at `key` + * in `scope`. + * + * @example + * var scope = {escape: ['a']} + * var descape = descapeFactory(scope, 'escape'); + * descape('\a \b'); // 'a \b' + * + * @param {string} value - Escaped string. + * @return {string} - Unescaped string. + */ + function descape(value) { + var prev = 0; + var index = value.indexOf(C_BACKSLASH); + var escape = scope[key]; + var queue = []; + var character; + + while (index !== -1) { + queue.push(value.slice(prev, index)); + prev = index + 1; + character = value.charAt(prev); + + /* + * If the following character is not a valid escape, + * add the slash. + */ + + if (!character || escape.indexOf(character) === -1) { + queue.push(C_BACKSLASH); + } + + index = value.indexOf(C_BACKSLASH, prev); + } + + queue.push(value.slice(prev)); + + return queue.join(EMPTY); + } + + return descape; +} + +/** + * Gets indentation information for a line. + * + * @example + * getIndent(' foo'); + * // {indent: 2, stops: {1: 0, 2: 1}} + * + * getIndent('\tfoo'); + * // {indent: 4, stops: {4: 0}} + * + * getIndent(' \tfoo'); + * // {indent: 4, stops: {1: 0, 2: 1, 4: 2}} + * + * getIndent('\t foo') + * // {indent: 6, stops: {4: 0, 5: 1, 6: 2}} + * + * @param {string} value - Indented line. + * @return {Object} - Indetation information. + */ +function getIndent(value) { + var index = 0; + var indent = 0; + var character = value.charAt(index); + var stops = {}; + var size; + + while (character in INDENTATION_CHARACTERS) { + size = INDENTATION_CHARACTERS[character]; + + indent += size; + + if (size > 1) { + indent = Math.floor(indent / size) * size; + } + + stops[indent] = index; + + character = value.charAt(++index); + } + + return { + 'indent': indent, + 'stops': stops + }; +} + +/** + * Remove the minimum indent from every line in `value`. + * Supports both tab, spaced, and mixed indentation (as + * well as possible). + * + * @example + * removeIndentation(' foo'); // 'foo' + * removeIndentation(' foo', 2); // ' foo' + * removeIndentation('\tfoo', 2); // ' foo' + * removeIndentation(' foo\n bar'); // ' foo\n bar' + * + * @param {string} value - Value to trim. + * @param {number?} [maximum] - Maximum indentation + * to remove. + * @return {string} - Unindented `value`. + */ +function removeIndentation(value, maximum) { + var values = value.split(C_NEWLINE); + var position = values.length + 1; + var minIndent = Infinity; + var matrix = []; + var index; + var indentation; + var stops; + var padding; + + values.unshift(repeat(C_SPACE, maximum) + C_EXCLAMATION_MARK); + + while (position--) { + indentation = getIndent(values[position]); + + matrix[position] = indentation.stops; + + if (trim(values[position]).length === 0) { + continue; + } + + if (indentation.indent) { + if (indentation.indent > 0 && indentation.indent < minIndent) { + minIndent = indentation.indent; + } + } else { + minIndent = Infinity; + + break; + } + } + + if (minIndent !== Infinity) { + position = values.length; + + while (position--) { + stops = matrix[position]; + index = minIndent; + + while (index && !(index in stops)) { + index--; + } + + if ( + trim(values[position]).length !== 0 && + minIndent && + index !== minIndent + ) { + padding = C_TAB; + } else { + padding = EMPTY; + } + + values[position] = padding + values[position].slice( + index in stops ? stops[index] + 1 : 0 + ); + } + } + + values.shift(); + + return values.join(C_NEWLINE); +} + +/** + * Tokenise a line. + * + * @example + * tokenizeNewline(eat, '\n\n'); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {boolean?} - `true` when matching. + */ +function tokenizeNewline(eat, value, silent) { + var character = value.charAt(0); + var length; + var subvalue; + var queue; + var index; + + if (character !== C_NEWLINE) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + index = 1; + length = value.length; + subvalue = C_NEWLINE; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + queue += character; + + if (character === C_NEWLINE) { + subvalue += queue; + queue = EMPTY; + } + + index++; + } + + eat(subvalue); +} + +/** + * Tokenise an indented code block. + * + * @example + * tokenizeIndentedCode(eat, '\tfoo'); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `code` node. + */ +function tokenizeIndentedCode(eat, value, silent) { + var self = this; + var index = -1; + var length = value.length; + var character; + var subvalue = EMPTY; + var content = EMPTY; + var subvalueQueue = EMPTY; + var contentQueue = EMPTY; + var blankQueue; + var indent; + + while (++index < length) { + character = value.charAt(index); + + if (indent) { + indent = false; + + subvalue += subvalueQueue; + content += contentQueue; + subvalueQueue = contentQueue = EMPTY; + + if (character === C_NEWLINE) { + subvalueQueue = contentQueue = character; + } else { + subvalue += character; + content += character; + + while (++index < length) { + character = value.charAt(index); + + if (!character || character === C_NEWLINE) { + contentQueue = subvalueQueue = character; + break; + } + + subvalue += character; + content += character; + } + } + } else if ( + character === C_SPACE && + value.charAt(index + 1) === C_SPACE && + value.charAt(index + 2) === C_SPACE && + value.charAt(index + 3) === C_SPACE + ) { + subvalueQueue += CODE_INDENT; + index += 3; + indent = true; + } else if (character === C_TAB) { + subvalueQueue += character; + indent = true; + } else { + blankQueue = EMPTY; + + while (character === C_TAB || character === C_SPACE) { + blankQueue += character; + character = value.charAt(++index); + } + + if (character !== C_NEWLINE) { + break; + } + + subvalueQueue += blankQueue + character; + contentQueue += character; + } + } + + if (content) { + if (silent) { + return true; + } + + return eat(subvalue)(self.renderCodeBlock(content)); + } +} + +/** + * Tokenise a fenced code block. + * + * @example + * tokenizeFencedCode(eat, '```js\nfoo()\n```'); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `code` node. + */ +function tokenizeFencedCode(eat, value, silent) { + var self = this; + var settings = self.options; + var length = value.length + 1; + var index = 0; + var subvalue = EMPTY; + var fenceCount; + var marker; + var character; + var flag; + var queue; + var content; + var exdentedContent; + var closing; + var exdentedClosing; + var indent; + var now; + + if (!settings.gfm) { + return; + } + + /* + * Eat initial spacing. + */ + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + break; + } + + subvalue += character; + index++; + } + + indent = index; // TODO: CHECK. + + /* + * Eat the fence. + */ + + character = value.charAt(index); + + if (character !== C_TILDE && character !== C_TICK) { + return; + } + + index++; + marker = character; + fenceCount = 1; + subvalue += character; + + while (index < length) { + character = value.charAt(index); + + if (character !== marker) { + break; + } + + subvalue += character; + fenceCount++; + index++; + } + + if (fenceCount < MIN_FENCE_COUNT) { + return; + } + + /* + * Eat spacing before flag. + */ + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + break; + } + + subvalue += character; + index++; + } + + /* + * Eat flag. + */ + + flag = queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if ( + character === C_NEWLINE || + character === C_TILDE || + character === C_TICK + ) { + break; + } + + if (character === C_SPACE || character === C_TAB) { + queue += character; + } else { + flag += queue + character; + queue = EMPTY; + } + + index++; + } + + character = value.charAt(index); + + if (character && character !== C_NEWLINE) { + return; + } + + if (silent) { + return true; + } + + now = eat.now(); + now.column += subvalue.length; + now.offset += subvalue.length; + + subvalue += flag; + flag = self.decode.raw(self.descape(flag), now); + + if (queue) { + subvalue += queue; + } + + queue = closing = exdentedClosing = content = exdentedContent = EMPTY; + + /* + * Eat content. + */ + + while (index < length) { + character = value.charAt(index); + content += closing; + exdentedContent += exdentedClosing; + closing = exdentedClosing = EMPTY; + + if (character !== C_NEWLINE) { + content += character; + exdentedClosing += character; + index++; + continue; + } + + /* + * Add the newline to `subvalue` if its the first + * character. Otherwise, add it to the `closing` + * queue. + */ + + if (!content) { + subvalue += character; + } else { + closing += character; + exdentedClosing += character; + } + + queue = EMPTY; + index++; + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE) { + break; + } + + queue += character; + index++; + } + + closing += queue; + exdentedClosing += queue.slice(indent); + + if (queue.length >= CODE_INDENT_LENGTH) { + continue; + } + + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character !== marker) { + break; + } + + queue += character; + index++; + } + + closing += queue; + exdentedClosing += queue; + + if (queue.length < fenceCount) { + continue; + } + + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + break; + } + + closing += character; + exdentedClosing += character; + index++; + } + + if (!character || character === C_NEWLINE) { + break; + } + } + + subvalue += content + closing; + + return eat(subvalue)(self.renderCodeBlock(exdentedContent, flag)); +} + +/** + * Tokenise an ATX-style heading. + * + * @example + * tokenizeATXHeading(eat, ' # foo'); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `heading` node. + */ +function tokenizeATXHeading(eat, value, silent) { + var self = this; + var settings = self.options; + var length = value.length + 1; + var index = -1; + var now = eat.now(); + var subvalue = EMPTY; + var content = EMPTY; + var character; + var queue; + var depth; + + /* + * Eat initial spacing. + */ + + while (++index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + index--; + break; + } + + subvalue += character; + } + + /* + * Eat hashes. + */ + + depth = 0; + length = index + MAX_ATX_COUNT + 1; + + while (++index <= length) { + character = value.charAt(index); + + if (character !== C_HASH) { + index--; + break; + } + + subvalue += character; + depth++; + } + + if ( + !depth || + (!settings.pedantic && value.charAt(index + 1) === C_HASH) + ) { + return; + } + + length = value.length + 1; + + /* + * Eat intermediate white-space. + */ + + queue = EMPTY; + + while (++index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + index--; + break; + } + + queue += character; + } + + /* + * Exit when not in pedantic mode without spacing. + */ + + if ( + !settings.pedantic && + !queue.length && + character && + character !== C_NEWLINE + ) { + return; + } + + if (silent) { + return true; + } + + /* + * Eat content. + */ + + subvalue += queue; + queue = content = EMPTY; + + while (++index < length) { + character = value.charAt(index); + + if (!character || character === C_NEWLINE) { + break; + } + + if ( + character !== C_SPACE && + character !== C_TAB && + character !== C_HASH + ) { + content += queue + character; + queue = EMPTY; + continue; + } + + while (character === C_SPACE || character === C_TAB) { + queue += character; + character = value.charAt(++index); + } + + while (character === C_HASH) { + queue += character; + character = value.charAt(++index); + } + + while (character === C_SPACE || character === C_TAB) { + queue += character; + character = value.charAt(++index); + } + + index--; + } + + now.column += subvalue.length; + now.offset += subvalue.length; + subvalue += content + queue; + + return eat(subvalue)(self.renderHeading(content, depth, now)); +} + +/** + * Tokenise a Setext-style heading. + * + * @example + * tokenizeSetextHeading(eat, 'foo\n==='); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `heading` node. + */ +function tokenizeSetextHeading(eat, value, silent) { + var self = this; + var now = eat.now(); + var length = value.length; + var index = -1; + var subvalue = EMPTY; + var content; + var queue; + var character; + var marker; + var depth; + + /* + * Eat initial indentation. + */ + + while (++index < length) { + character = value.charAt(index); + + if (character !== C_SPACE || index >= MAX_LINE_HEADING_INDENT) { + index--; + break; + } + + subvalue += character; + } + + /* + * Eat content. + */ + + content = queue = EMPTY; + + while (++index < length) { + character = value.charAt(index); + + if (character === C_NEWLINE) { + index--; + break; + } + + if (character === C_SPACE || character === C_TAB) { + queue += character; + } else { + content += queue + character; + queue = EMPTY; + } + } + + now.column += subvalue.length; + now.offset += subvalue.length; + subvalue += content + queue; + + /* + * Ensure the content is followed by a newline and a + * valid marker. + */ + + character = value.charAt(++index); + marker = value.charAt(++index); + + if (character !== C_NEWLINE || !SETEXT_MARKERS[marker]) { + return; + } + + subvalue += character; + + /* + * Eat Setext-line. + */ + + queue = marker; + depth = SETEXT_MARKERS[marker]; + + while (++index < length) { + character = value.charAt(index); + + if (character !== marker) { + if (character !== C_NEWLINE) { + return; + } + + index--; + break; + } + + queue += character; + } + + if (silent) { + return true; + } + + return eat(subvalue + queue)(self.renderHeading(content, depth, now)); +} + +/** + * Tokenise a horizontal rule. + * + * @example + * tokenizeThematicBreak(eat, '***'); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `thematicBreak` node. + */ +function tokenizeThematicBreak(eat, value, silent) { + var self = this; + var index = -1; + var length = value.length + 1; + var subvalue = EMPTY; + var character; + var marker; + var markerCount; + var queue; + + while (++index < length) { + character = value.charAt(index); + + if (character !== C_TAB && character !== C_SPACE) { + break; + } + + subvalue += character; + } + + if (RULE_MARKERS[character] !== true) { + return; + } + + marker = character; + subvalue += character; + markerCount = 1; + queue = EMPTY; + + while (++index < length) { + character = value.charAt(index); + + if (character === marker) { + markerCount++; + subvalue += queue + marker; + queue = EMPTY; + } else if (character === C_SPACE) { + queue += character; + } else if ( + markerCount >= THEMATIC_BREAK_MARKER_COUNT && + (!character || character === C_NEWLINE) + ) { + subvalue += queue; + + if (silent) { + return true; + } + + return eat(subvalue)(self.renderVoid(T_THEMATIC_BREAK)); + } else { + return; + } + } +} + +/** + * Tokenise a blockquote. + * + * @example + * tokenizeBlockquote(eat, '> Foo'); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `blockquote` node. + */ +function tokenizeBlockquote(eat, value, silent) { + var self = this; + var commonmark = self.options.commonmark; + var now = eat.now(); + var indent = self.indent(now.line); + var length = value.length; + var values = []; + var contents = []; + var indents = []; + var add; + var tokenizers; + var index = 0; + var character; + var rest; + var nextIndex; + var content; + var line; + var startIndex; + var prefixed; + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + break; + } + + index++; + } + + if (value.charAt(index) !== C_GT) { + return; + } + + if (silent) { + return true; + } + + tokenizers = self.blockTokenizers; + index = 0; + + while (index < length) { + nextIndex = value.indexOf(C_NEWLINE, index); + startIndex = index; + prefixed = false; + + if (nextIndex === -1) { + nextIndex = length; + } + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + break; + } + + index++; + } + + if (value.charAt(index) === C_GT) { + index++; + prefixed = true; + + if (value.charAt(index) === C_SPACE) { + index++; + } + } else { + index = startIndex; + } + + content = value.slice(index, nextIndex); + + if (!prefixed && !trim(content)) { + index = startIndex; + break; + } + + if (!prefixed) { + rest = value.slice(index); + + if ( + commonmark && + ( + tokenizers.indentedCode.call(self, eat, rest, true) || + tokenizers.fencedCode.call(self, eat, rest, true) || + tokenizers.atxHeading.call(self, eat, rest, true) || + tokenizers.setextHeading.call(self, eat, rest, true) || + tokenizers.thematicBreak.call(self, eat, rest, true) || + tokenizers.html.call(self, eat, rest, true) || + tokenizers.list.call(self, eat, rest, true) + ) + ) { + break; + } + + if ( + !commonmark && + ( + tokenizers.definition.call(self, eat, rest, true) || + tokenizers.footnote.call(self, eat, rest, true) + ) + ) { + break; + } + } + + line = startIndex === index ? + content : + value.slice(startIndex, nextIndex); + + indents.push(index - startIndex); + values.push(line); + contents.push(content); + + index = nextIndex + 1; + } + + index = -1; + length = indents.length; + add = eat(values.join(C_NEWLINE)); + + while (++index < length) { + indent(indents[index]); + } + + return add(self.renderBlockquote(contents.join(C_NEWLINE), now)); +} + +/** + * Tokenise a list. + * + * @example + * tokenizeList(eat, '- Foo'); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `list` node. + */ +function tokenizeList(eat, value, silent) { + var self = this; + var commonmark = self.options.commonmark; + var pedantic = self.options.pedantic; + var tokenizers = self.blockTokenizers; + var markers; + var index = 0; + var length = value.length; + var start = null; + var queue; + var ordered; + var character; + var marker; + var nextIndex; + var startIndex; + var prefixed; + var currentMarker; + var content; + var line; + var prevEmpty; + var empty; + var items; + var allLines; + var emptyLines; + var item; + var enterTop; + var exitBlockquote; + var isLoose; + var node; + var now; + var end; + var indented; + var size; + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + break; + } + + index++; + } + + character = value.charAt(index); + + markers = commonmark ? + LIST_ORDERED_COMMONMARK_MARKERS : + LIST_ORDERED_MARKERS; + + if (LIST_UNORDERED_MARKERS[character] === true) { + marker = character; + ordered = false; + } else { + ordered = true; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (!isNumeric(character)) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + + if (!queue || markers[character] !== true) { + return; + } + + start = parseInt(queue, 10); + marker = character; + } + + character = value.charAt(++index); + + if (character !== C_SPACE && character !== C_TAB) { + return; + } + + if (silent) { + return true; + } + + index = 0; + items = []; + allLines = []; + emptyLines = []; + + while (index < length) { + nextIndex = value.indexOf(C_NEWLINE, index); + startIndex = index; + prefixed = false; + indented = false; + + if (nextIndex === -1) { + nextIndex = length; + } + + end = index + TAB_SIZE; + size = 0; + + while (index < length) { + character = value.charAt(index); + + if (character === C_TAB) { + size += TAB_SIZE - size % TAB_SIZE; + } else if (character === C_SPACE) { + size++; + } else { + break; + } + + index++; + } + + if (size >= TAB_SIZE) { + indented = true; + } + + if (item && size >= item.indent) { + indented = true; + } + + character = value.charAt(index); + currentMarker = null; + + if (!indented) { + if (LIST_UNORDERED_MARKERS[character] === true) { + currentMarker = character; + index++; + size++; + } else { + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (!isNumeric(character)) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + index++; + + if (queue && markers[character] === true) { + currentMarker = character; + size += queue.length + 1; + } + } + + if (currentMarker) { + character = value.charAt(index); + + if (character === C_TAB) { + size += TAB_SIZE - size % TAB_SIZE; + index++; + } else if (character === C_SPACE) { + end = index + TAB_SIZE; + + while (index < end) { + if (value.charAt(index) !== C_SPACE) { + break; + } + + index++; + size++; + } + + if (index === end && value.charAt(index) === C_SPACE) { + index -= TAB_SIZE - 1; + size -= TAB_SIZE - 1; + } + } else if ( + character !== C_NEWLINE && + character !== EMPTY + ) { + currentMarker = null; + } + } + } + + if (currentMarker) { + if (commonmark && marker !== currentMarker) { + break; + } + + prefixed = true; + } else { + if ( + !commonmark && + !indented && + value.charAt(startIndex) === C_SPACE + ) { + indented = true; + } else if ( + commonmark && + item + ) { + indented = size >= item.indent || size > TAB_SIZE; + } + + prefixed = false; + index = startIndex; + } + + line = value.slice(startIndex, nextIndex); + content = startIndex === index ? line : value.slice(index, nextIndex); + + if (currentMarker && RULE_MARKERS[currentMarker] === true) { + if ( + tokenizers.thematicBreak.call(self, eat, line, true) + ) { + break; + } + } + + prevEmpty = empty; + empty = !trim(content).length; + + if (indented && item) { + item.value = item.value.concat(emptyLines, line); + allLines = allLines.concat(emptyLines, line); + emptyLines = []; + } else if (prefixed) { + if (emptyLines.length) { + item.value.push(EMPTY); + item.trail = emptyLines.concat(); + } + + item = { + // 'bullet': value.slice(startIndex, index), + 'value': [line], + 'indent': size, + 'trail': [] + }; + + items.push(item); + allLines = allLines.concat(emptyLines, line); + emptyLines = []; + } else if (empty) { + // TODO: disable when in pedantic-mode. + if (prevEmpty) { + break; + } + + emptyLines.push(line); + } else { + if (prevEmpty) { + break; + } + + if ( + !pedantic && + ( + tokenizers.fencedCode.call(self, eat, line, true) || + tokenizers.thematicBreak.call(self, eat, line, true) + ) + ) { + break; + } + + if (!commonmark) { + if ( + tokenizers.definition.call(self, eat, line, true) || + tokenizers.footnote.call(self, eat, line, true) + ) { + break; + } + } + + item.value = item.value.concat(emptyLines, line); + allLines = allLines.concat(emptyLines, line); + emptyLines = []; + } + + index = nextIndex + 1; + } + + node = eat(allLines.join(C_NEWLINE)).reset({ + 'type': T_LIST, + 'ordered': ordered, + 'start': start, + 'loose': null, + 'children': [] + }); + + enterTop = self.enterList(); + exitBlockquote = self.enterBlock(); + isLoose = false; + index = -1; + length = items.length; + + while (++index < length) { + item = items[index].value.join(C_NEWLINE); + now = eat.now(); + + item = eat(item)(self.renderListItem(item, now), node); + + if (item.loose) { + isLoose = true; + } + + item = items[index].trail.join(C_NEWLINE); + + if (index !== length - 1) { + item += C_NEWLINE; + } + + eat(item); + } + + enterTop(); + exitBlockquote(); + + node.loose = isLoose; + + return node; +} + +/** + * Try to match comment. + * + * @param {string} value - Value to parse. + * @param {Object} settings - Configuration as available on + * a parser. + * @return {string?} - When applicable, the comment at the + * start of `value`. + */ +function eatHTMLComment(value, settings) { + var index = COMMENT_START_LENGTH; + var queue = COMMENT_START; + var length = value.length; + var commonmark = settings.commonmark; + var character; + var hasNonDash; + + if (value.slice(0, index) === queue) { + while (index < length) { + character = value.charAt(index); + + if ( + character === COMMENT_END_CHAR && + value.slice(index, index + COMMENT_END_LENGTH) === COMMENT_END + ) { + return queue + COMMENT_END; + } + + if (commonmark) { + if (character === C_GT && !hasNonDash) { + return; + } + + if (character === C_DASH) { + if (value.charAt(index + 1) === C_DASH) { + return; + } + } else { + hasNonDash = true; + } + } + + queue += character; + index++; + } + } +} + +/** + * Try to match CDATA. + * + * @param {string} value - Value to parse. + * @return {string?} - When applicable, the CDATA at the + * start of `value`. + */ +function eatHTMLCDATA(value) { + var index = CDATA_START_LENGTH; + var queue = value.slice(0, index); + var length = value.length; + var character; + + if (queue.toUpperCase() === CDATA_START) { + while (index < length) { + character = value.charAt(index); + + if ( + character === CDATA_END_CHAR && + value.slice(index, index + CDATA_END_LENGTH) === CDATA_END + ) { + return queue + CDATA_END; + } + + queue += character; + index++; + } + } +} + +/** + * Try to match a processing instruction. + * + * @param {string} value - Value to parse. + * @return {string?} - When applicable, the processing + * instruction at the start of `value`. + */ +function eatHTMLProcessingInstruction(value) { + var index = 0; + var queue = EMPTY; + var length = value.length; + var character; + + if ( + value.charAt(index) === C_LT && + value.charAt(++index) === C_QUESTION_MARK + ) { + queue = C_LT + C_QUESTION_MARK; + index++; + + while (index < length) { + character = value.charAt(index); + + if ( + character === C_QUESTION_MARK && + value.charAt(index + 1) === C_GT + ) { + return queue + character + C_GT; + } + + queue += character; + index++; + } + } +} + +/** + * Try to match a declaration. + * + * @param {string} value - Value to parse. + * @return {string?} - When applicable, the declaration at + * the start of `value`. + */ +function eatHTMLDeclaration(value) { + var index = 0; + var length = value.length; + var queue = EMPTY; + var subqueue = EMPTY; + var character; + + if ( + value.charAt(index) === C_LT && + value.charAt(++index) === C_EXCLAMATION_MARK + ) { + queue = C_LT + C_EXCLAMATION_MARK; + index++; + + /* + * Eat as many alphabetic characters as + * possible. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isAlphabetic(character)) { + break; + } + + subqueue += character; + index++; + } + + character = value.charAt(index); + + if (!subqueue || !isWhiteSpace(character)) { + return; + } + + queue += subqueue + character; + index++; + + while (index < length) { + character = value.charAt(index); + + if (character === C_GT) { + return queue; + } + + queue += character; + index++; + } + } +} + +/** + * Try to match a closing tag. + * + * @param {string} value - Value to parse. + * @param {boolean?} [isBlock] - Whether the tag-name + * must be a known block-level node to match. + * @return {string?} - When applicable, the closing tag at + * the start of `value`. + */ +function eatHTMLClosingTag(value, isBlock) { + var index = 0; + var length = value.length; + var queue = EMPTY; + var subqueue = EMPTY; + var character; + + if ( + value.charAt(index) === C_LT && + value.charAt(++index) === C_SLASH + ) { + queue = C_LT + C_SLASH; + subqueue = character = value.charAt(++index); + + if (!isAlphabetic(character)) { + return; + } + + index++; + + /* + * Eat as many alphabetic characters as + * possible. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isAlphabetic(character) && !isNumeric(character)) { + break; + } + + subqueue += character; + index++; + } + + if (isBlock && blockElements.indexOf(subqueue.toLowerCase()) === -1) { + return; + } + + queue += subqueue; + + /* + * Eat white-space. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + queue += character; + index++; + } + + if (value.charAt(index) === C_GT) { + return queue + C_GT; + } + } +} + +/** + * Try to match an opening tag. + * + * @param {string} value - Value to parse. + * @param {boolean?} [isBlock] - Whether the tag-name + * must be a known block-level node to match. + * @return {string?} - When applicable, the opening tag at + * the start of `value`. + */ +function eatHTMLOpeningTag(value, isBlock) { + var index = 0; + var length = value.length; + var queue = EMPTY; + var subqueue = EMPTY; + var character = value.charAt(index); + var hasEquals; + var test; + + if (character === C_LT) { + queue = character; + subqueue = character = value.charAt(++index); + + if (!isAlphabetic(character)) { + return; + } + + index++; + + /* + * Eat as many alphabetic characters as + * possible. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isAlphabetic(character) && !isNumeric(character)) { + break; + } + + subqueue += character; + index++; + } + + if (isBlock && blockElements.indexOf(subqueue.toLowerCase()) === -1) { + return; + } + + queue += subqueue; + subqueue = EMPTY; + + /* + * Find attributes. + */ + + while (index < length) { + /* + * Eat white-space. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + subqueue += character; + index++; + } + + if (!subqueue) { + break; + } + + /* + * Eat an attribute name. + */ + + queue += subqueue; + subqueue = EMPTY; + character = value.charAt(index); + + if ( + isAlphabetic(character) || + character === C_UNDERSCORE || + character === C_COLON + ) { + subqueue = character; + index++; + + while (index < length) { + character = value.charAt(index); + + if ( + !isAlphabetic(character) && + !isNumeric(character) && + character !== C_UNDERSCORE && + character !== C_COLON && + character !== C_DOT && + character !== C_DASH + ) { + break; + } + + subqueue += character; + index++; + } + } + + if (!subqueue) { + break; + } + + queue += subqueue; + subqueue = EMPTY; + hasEquals = false; + + /* + * Eat zero or more white-space and one + * equals sign. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + if (!hasEquals && character === C_EQUALS) { + hasEquals = true; + } else { + break; + } + } + + subqueue += character; + index++; + } + + queue += subqueue; + subqueue = EMPTY; + + if (!hasEquals) { + queue += subqueue; + } else { + character = value.charAt(index); + queue += subqueue; + + if (character === C_DOUBLE_QUOTE) { + test = isDoubleQuotedAttributeCharacter; + subqueue = character; + index++; + } else if (character === C_SINGLE_QUOTE) { + test = isSingleQuotedAttributeCharacter; + subqueue = character; + index++; + } else { + test = isUnquotedAttributeCharacter; + subqueue = EMPTY; + } + + while (index < length) { + character = value.charAt(index); + + if (!test(character)) { + break; + } + + subqueue += character; + index++; + } + + character = value.charAt(index); + index++; + + if (!test.delimiter) { + if (!subqueue.length) { + return; + } + + index--; + } else if (character === test.delimiter) { + subqueue += character; + } else { + return; + } + + queue += subqueue; + subqueue = EMPTY; + } + } + + /* + * More white-space is already eaten by the + * attributes subroutine. + */ + + character = value.charAt(index); + + /* + * Eat an optional backslash (for self-closing + * tags). + */ + + if (character === C_SLASH) { + queue += character; + character = value.charAt(++index); + } + + return character === C_GT ? queue + character : null; + } +} + +/** + * Tokenise HTML. + * + * @example + * tokenizeBlockHTML(eat, '<span>foo</span>'); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `html` node. + */ +function tokenizeBlockHTML(eat, value, silent) { + var self = this; + var index = 0; + var length = value.length; + var subvalue = EMPTY; + var offset; + var lineCount; + var character; + var queue; + + /* + * Eat initial spacing. + */ + + while (index < length) { + character = value.charAt(index); + + if (character !== C_TAB && character !== C_SPACE) { + break; + } + + subvalue += character; + index++; + } + + offset = index; + value = value.slice(offset); + + /* + * Try to eat an HTML thing. + */ + + queue = eatHTMLComment(value, self.options) || + eatHTMLCDATA(value) || + eatHTMLProcessingInstruction(value) || + eatHTMLDeclaration(value) || + eatHTMLClosingTag(value, true) || + eatHTMLOpeningTag(value, true); + + if (!queue) { + return; + } + + if (silent) { + return true; + } + + subvalue += queue; + index = subvalue.length - offset; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === C_NEWLINE) { + queue += character; + lineCount++; + } else if (queue.length < MIN_CLOSING_HTML_NEWLINE_COUNT) { + subvalue += queue + character; + queue = EMPTY; + } else { + break; + } + + index++; + } + + return eat(subvalue)(self.renderRaw(T_HTML, subvalue)); +} + +/** + * Tokenise a definition. + * + * @example + * var value = '[foo]: http://example.com "Example Domain"'; + * tokenizeDefinition(eat, value); + * + * @property {boolean} notInList + * @property {boolean} notInBlock + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `definition` node. + */ +function tokenizeDefinition(eat, value, silent) { + var self = this; + var commonmark = self.options.commonmark; + var index = 0; + var length = value.length; + var subvalue = EMPTY; + var beforeURL; + var beforeTitle; + var queue; + var character; + var test; + var identifier; + var url; + var title; + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + break; + } + + subvalue += character; + index++; + } + + character = value.charAt(index); + + if (character !== C_BRACKET_OPEN) { + return; + } + + index++; + subvalue += character; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === C_BRACKET_CLOSE) { + break; + } else if (character === C_BACKSLASH) { + queue += character; + index++; + character = value.charAt(index); + } + + queue += character; + index++; + } + + if ( + !queue || + value.charAt(index) !== C_BRACKET_CLOSE || + value.charAt(index + 1) !== C_COLON + ) { + return; + } + + identifier = queue; + subvalue += queue + C_BRACKET_CLOSE + C_COLON; + index = subvalue.length; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if ( + character !== C_TAB && + character !== C_SPACE && + character !== C_NEWLINE + ) { + break; + } + + subvalue += character; + index++; + } + + character = value.charAt(index); + queue = EMPTY; + beforeURL = subvalue; + + if (character === C_LT) { + index++; + + while (index < length) { + character = value.charAt(index); + + if (!isEnclosedURLCharacter(character)) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + + if (character !== isEnclosedURLCharacter.delimiter) { + if (commonmark) { + return; + } + + index -= queue.length + 1; + queue = EMPTY; + } else { + subvalue += C_LT + queue + character; + index++; + } + } + + if (!queue) { + while (index < length) { + character = value.charAt(index); + + if (!isUnclosedURLCharacter(character)) { + break; + } + + queue += character; + index++; + } + + subvalue += queue; + } + + if (!queue) { + return; + } + + url = queue; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if ( + character !== C_TAB && + character !== C_SPACE && + character !== C_NEWLINE + ) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + test = null; + + if (character === C_DOUBLE_QUOTE) { + test = C_DOUBLE_QUOTE; + } else if (character === C_SINGLE_QUOTE) { + test = C_SINGLE_QUOTE; + } else if (character === C_PAREN_OPEN) { + test = C_PAREN_CLOSE; + } + + if (!test) { + queue = EMPTY; + index = subvalue.length; + } else if (!queue) { + return; + } else { + subvalue += queue + character; + index = subvalue.length; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === test) { + break; + } + + if (character === C_NEWLINE) { + index++; + character = value.charAt(index); + + if (character === C_NEWLINE || character === test) { + return; + } + + queue += C_NEWLINE; + } + + queue += character; + index++; + } + + character = value.charAt(index); + + if (character !== test) { + return; + } + + beforeTitle = subvalue; + subvalue += queue + character; + index++; + title = queue; + queue = EMPTY; + } + + while (index < length) { + character = value.charAt(index); + + if (character !== C_TAB && character !== C_SPACE) { + break; + } + + subvalue += character; + index++; + } + + character = value.charAt(index); + + if (!character || character === C_NEWLINE) { + if (silent) { + return true; + } + + beforeURL = eat(beforeURL).test().end; + url = self.decode.raw(self.descape(url), beforeURL); + + if (title) { + beforeTitle = eat(beforeTitle).test().end; + title = self.decode.raw(self.descape(title), beforeTitle); + } + + return eat(subvalue)({ + 'type': T_DEFINITION, + 'identifier': normalize(identifier), + 'title': title || null, + 'url': url + }); + } +} + +tokenizeDefinition.notInList = true; +tokenizeDefinition.notInBlock = true; + +/** + * Tokenise YAML front matter. + * + * @example + * tokenizeYAMLFrontMatter(eat, '---\nfoo: bar\n---'); + * + * @property {boolean} onlyAtStart + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `yaml` node. + */ +function tokenizeYAMLFrontMatter(eat, value, silent) { + var self = this; + var subvalue; + var content; + var index; + var length; + var character; + var queue; + + if ( + !self.options.yaml || + value.charAt(0) !== C_DASH || + value.charAt(1) !== C_DASH || + value.charAt(2) !== C_DASH || + value.charAt(3) !== C_NEWLINE + ) { + return; + } + + subvalue = YAML_FENCE + C_NEWLINE; + content = queue = EMPTY; + index = 3; + length = value.length; + + while (++index < length) { + character = value.charAt(index); + + if ( + character === C_DASH && + (queue || !content) && + value.charAt(index + 1) === C_DASH && + value.charAt(index + 2) === C_DASH + ) { + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + subvalue += queue + YAML_FENCE; + + return eat(subvalue)(self.renderRaw(T_YAML, content)); + } + + if (character === C_NEWLINE) { + queue += character; + } else { + subvalue += queue + character; + content += queue + character; + queue = EMPTY; + } + } +} + +tokenizeYAMLFrontMatter.onlyAtStart = true; + +/** + * Tokenise a footnote definition. + * + * @example + * tokenizeFootnoteDefinition(eat, '[^foo]: Bar.'); + * + * @property {boolean} notInList + * @property {boolean} notInBlock + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `footnoteDefinition` node. + */ +function tokenizeFootnoteDefinition(eat, value, silent) { + var self = this; + var index; + var length; + var subvalue; + var now; + var indent; + var content; + var queue; + var subqueue; + var character; + var identifier; + + if (!self.options.footnotes) { + return; + } + + index = 0; + length = value.length; + subvalue = EMPTY; + now = eat.now(); + indent = self.indent(now.line); + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + subvalue += character; + index++; + } + + if ( + value.charAt(index) !== C_BRACKET_OPEN || + value.charAt(index + 1) !== C_CARET + ) { + return; + } + + subvalue += C_BRACKET_OPEN + C_CARET; + index = subvalue.length; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === C_BRACKET_CLOSE) { + break; + } else if (character === C_BACKSLASH) { + queue += character; + index++; + character = value.charAt(index); + } + + queue += character; + index++; + } + + if ( + !queue || + value.charAt(index) !== C_BRACKET_CLOSE || + value.charAt(index + 1) !== C_COLON + ) { + return; + } + + if (silent) { + return true; + } + + identifier = normalize(queue); + subvalue += queue + C_BRACKET_CLOSE + C_COLON; + index = subvalue.length; + + while (index < length) { + character = value.charAt(index); + + if ( + character !== C_TAB && + character !== C_SPACE + ) { + break; + } + + subvalue += character; + index++; + } + + now.column += subvalue.length; + now.offset += subvalue.length; + queue = content = subqueue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === C_NEWLINE) { + subqueue = character; + index++; + + while (index < length) { + character = value.charAt(index); + + if (character !== C_NEWLINE) { + break; + } + + subqueue += character; + index++; + } + + queue += subqueue; + subqueue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE) { + break; + } + + subqueue += character; + index++; + } + + if (!subqueue.length) { + break; + } + + queue += subqueue; + } + + if (queue) { + content += queue; + queue = EMPTY; + } + + content += character; + index++; + } + + subvalue += content; + + content = content.replace(EXPRESSION_INITIAL_TAB, function (line) { + indent(line.length); + + return EMPTY; + }); + + return eat(subvalue)( + self.renderFootnoteDefinition(identifier, content, now) + ); +} + +tokenizeFootnoteDefinition.notInList = true; +tokenizeFootnoteDefinition.notInBlock = true; + +/** + * Tokenise a table. + * + * @example + * tokenizeTable(eat, ' | foo |\n | --- |\n | bar |'); + * + * @property {boolean} notInList + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `table` node. + */ +function tokenizeTable(eat, value, silent) { + var self = this; + var index; + var alignments; + var alignment; + var subvalue; + var row; + var length; + var lines; + var queue; + var character; + var hasDash; + var align; + var cell; + var preamble; + var count; + var opening; + var now; + var position; + var lineCount; + var line; + var rows; + var table; + var lineIndex; + var pipeIndex; + var first; + + /* + * Exit when not in gfm-mode. + */ + + if (!self.options.gfm) { + return; + } + + /* + * Get the rows. + * Detecting tables soon is hard, so there are some + * checks for performance here, such as the minimum + * number of rows, and allowed characters in the + * alignment row. + */ + + index = lineCount = 0; + length = value.length + 1; + lines = []; + + while (index < length) { + lineIndex = value.indexOf(C_NEWLINE, index); + pipeIndex = value.indexOf(C_PIPE, index + 1); + + if (lineIndex === -1) { + lineIndex = value.length; + } + + if ( + pipeIndex === -1 || + pipeIndex > lineIndex + ) { + if (lineCount < MIN_TABLE_ROWS) { + return; + } + + break; + } + + lines.push(value.slice(index, lineIndex)); + lineCount++; + index = lineIndex + 1; + } + + /* + * Parse the alignment row. + */ + + subvalue = lines.join(C_NEWLINE); + alignments = lines.splice(1, 1)[0] || []; + index = 0; + length = alignments.length; + lineCount--; + alignment = false; + align = []; + + while (index < length) { + character = alignments.charAt(index); + + if (character === C_PIPE) { + hasDash = null; + + if (alignment === false) { + if (first === false) { + return; + } + } else { + align.push(alignment); + alignment = false; + } + + first = false; + } else if (character === C_DASH) { + hasDash = true; + alignment = alignment || TABLE_ALIGN_NONE; + } else if (character === C_COLON) { + if (alignment === TABLE_ALIGN_LEFT) { + alignment = TABLE_ALIGN_CENTER; + } else if (hasDash && alignment === TABLE_ALIGN_NONE) { + alignment = TABLE_ALIGN_RIGHT; + } else { + alignment = TABLE_ALIGN_LEFT; + } + } else if (!isWhiteSpace(character)) { + return; + } + + index++; + } + + if (alignment !== false) { + align.push(alignment); + } + + /* + * Exit when without enough columns. + */ + + if (align.length < MIN_TABLE_COLUMNS) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + /* + * Parse the rows. + */ + + position = -1; + rows = []; + + table = eat(subvalue).reset({ + 'type': T_TABLE, + 'align': align, + 'children': rows + }); + + while (++position < lineCount) { + line = lines[position]; + row = self.renderParent(position ? T_TABLE_ROW : T_TABLE_HEADER, []); + + /* + * Eat a newline character when this is not the + * first row. + */ + + if (position) { + eat(C_NEWLINE); + } + + /* + * Eat the row. + */ + + eat(line).reset(row, table); + + length = line.length + 1; + index = 0; + queue = EMPTY; + cell = EMPTY; + preamble = true; + count = opening = null; + + while (index < length) { + character = line.charAt(index); + + if (character === C_TAB || character === C_SPACE) { + if (cell) { + queue += character; + } else { + eat(character); + } + + index++; + continue; + } + + if (character === EMPTY || character === C_PIPE) { + if (preamble) { + eat(character); + } else { + if (character && opening) { + queue += character; + index++; + continue; + } + + if ((cell || character) && !preamble) { + subvalue = cell; + + if (queue.length > 1) { + if (character) { + subvalue += queue.slice(0, queue.length - 1); + queue = queue.charAt(queue.length - 1); + } else { + subvalue += queue; + queue = EMPTY; + } + } + + now = eat.now(); + + eat(subvalue)( + self.renderInline(T_TABLE_CELL, cell, now), row + ); + } + + eat(queue + character); + + queue = EMPTY; + cell = EMPTY; + } + } else { + if (queue) { + cell += queue; + queue = EMPTY; + } + + cell += character; + + if (character === C_BACKSLASH && index !== length - 2) { + cell += line.charAt(index + 1); + index++; + } + + if (character === C_TICK) { + count = 1; + + while (line.charAt(index + 1) === character) { + cell += character; + index++; + count++; + } + + if (!opening) { + opening = count; + } else if (count >= opening) { + opening = 0; + } + } + } + + preamble = false; + index++; + } + + /* + * Eat the alignment row. + */ + + if (!position) { + eat(C_NEWLINE + alignments); + } + } + + return table; +} + +tokenizeTable.notInList = true; + +/** + * Tokenise a paragraph node. + * + * @example + * tokenizeParagraph(eat, 'Foo.'); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `paragraph` node. + */ +function tokenizeParagraph(eat, value, silent) { + var self = this; + var settings = self.options; + var commonmark = settings.commonmark; + var gfm = settings.gfm; + var tokenizers = self.blockTokenizers; + var index = value.indexOf(C_NEWLINE); + var length = value.length; + var position; + var subvalue; + var character; + var size; + var now; + + while (index < length) { + /* + * Eat everything if there’s no following newline. + */ + + if (index === -1) { + index = length; + break; + } + + /* + * Stop if the next character is NEWLINE. + */ + + if (value.charAt(index + 1) === C_NEWLINE) { + break; + } + + /* + * In commonmark-mode, following indented lines + * are part of the paragraph. + */ + + if (commonmark) { + size = 0; + position = index + 1; + + while (position < length) { + character = value.charAt(position); + + if (character === C_TAB) { + size = TAB_SIZE; + break; + } else if (character === C_SPACE) { + size++; + } else { + break; + } + + position++; + } + + if (size >= TAB_SIZE) { + index = value.indexOf(C_NEWLINE, index + 1); + continue; + } + } + + /* + * Check if the following code contains a possible + * block. + */ + + subvalue = value.slice(index + 1); + + if ( + tokenizers.thematicBreak.call(self, eat, subvalue, true) || + tokenizers.atxHeading.call(self, eat, subvalue, true) || + tokenizers.fencedCode.call(self, eat, subvalue, true) || + tokenizers.blockquote.call(self, eat, subvalue, true) || + tokenizers.html.call(self, eat, subvalue, true) + ) { + break; + } + + /* + * Break if the following line starts a list, when + * already in a list, or when in commonmark, or when + * in gfm mode and the bullet is *not* numeric. + */ + + if ( + tokenizers.list.call(self, eat, subvalue, true) && + ( + self.inList || + commonmark || + (gfm && !isNumeric(trim.left(subvalue).charAt(0))) + ) + ) { + break; + } + + if ( + !commonmark && + ( + tokenizers.setextHeading.call(self, eat, subvalue, true) || + tokenizers.definition.call(self, eat, subvalue, true) || + tokenizers.footnote.call(self, eat, subvalue, true) + ) + ) { + break; + } + + position = index; + index = value.indexOf(C_NEWLINE, index + 1); + + if (index !== -1 && trim(value.slice(position, index)) === EMPTY) { + index = position; + break; + } + } + + subvalue = value.slice(0, index); + + if (trim(subvalue) === EMPTY) { + eat(subvalue); + + return null; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + now = eat.now(); + subvalue = trimTrailingLines(subvalue); + + return eat(subvalue)(self.renderInline(T_PARAGRAPH, subvalue, now)); +} + +/** + * Tokenise a text node. + * + * @example + * tokenizeText(eat, 'foo'); + * + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `text` node. + */ +function tokenizeText(eat, value, silent) { + var self = this; + var methods; + var tokenizers; + var index; + var length; + var subvalue; + var position; + var tokenizer; + var name; + var min; + var now; + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + methods = self.inlineMethods; + length = methods.length; + tokenizers = self.inlineTokenizers; + index = -1; + min = value.length; + + while (++index < length) { + name = methods[index]; + + if (name === 'text' || !tokenizers[name]) { + continue; + } + + tokenizer = tokenizers[name].locator; + + if (!tokenizer) { + eat.file.fail(ERR_MISSING_LOCATOR + C_TICK + name + C_TICK); + continue; + } + + position = tokenizer.call(self, value, 1); + + if (position !== -1 && position < min) { + min = position; + } + } + + subvalue = value.slice(0, min); + now = eat.now(); + + self.decode(subvalue, now, function (content, position, source) { + eat(source || content)(self.renderRaw(T_TEXT, content)); + }); +} + +/** + * Create a code-block node. + * + * @example + * renderCodeBlock('foo()', 'js', now()); + * + * @param {string?} [value] - Code. + * @param {string?} [language] - Optional language flag. + * @param {Function} eat - Eater. + * @return {Object} - `code` node. + */ +function renderCodeBlock(value, language) { + return { + 'type': T_CODE, + 'lang': language || null, + 'value': trimTrailingLines(value || EMPTY) + }; +} + +/** + * Create a list-item using overly simple mechanics. + * + * @example + * renderPedanticListItem('- _foo_', now()); + * + * @param {string} value - List-item. + * @param {Object} position - List-item location. + * @return {string} - Cleaned `value`. + */ +function renderPedanticListItem(value, position) { + var self = this; + var indent = self.indent(position.line); + + /** + * A simple replacer which removed all matches, + * and adds their length to `offset`. + * + * @param {string} $0 - Indentation to subtract. + * @return {string} - An empty string. + */ + function replacer($0) { + indent($0.length); + + return EMPTY; + } + + /* + * Remove the list-item’s bullet. + */ + + value = value.replace(EXPRESSION_PEDANTIC_BULLET, replacer); + + /* + * The initial line was also matched by the below, so + * we reset the `line`. + */ + + indent = self.indent(position.line); + + return value.replace(EXPRESSION_INITIAL_INDENT, replacer); +} + +/** + * Create a list-item using sane mechanics. + * + * @example + * renderNormalListItem('- _foo_', now()); + * + * @param {string} value - List-item. + * @param {Object} position - List-item location. + * @return {string} - Cleaned `value`. + */ +function renderNormalListItem(value, position) { + var self = this; + var indent = self.indent(position.line); + var max; + var bullet; + var rest; + var lines; + var trimmedLines; + var index; + var length; + + /* + * Remove the list-item’s bullet. + */ + + value = value.replace(EXPRESSION_BULLET, function ($0, $1, $2, $3, $4) { + bullet = $1 + $2 + $3; + rest = $4; + + /* + * Make sure that the first nine numbered list items + * can indent with an extra space. That is, when + * the bullet did not receive an extra final space. + */ + + if (Number($2) < 10 && bullet.length % 2 === 1) { + $2 = C_SPACE + $2; + } + + max = $1 + repeat(C_SPACE, $2.length) + $3; + + return max + rest; + }); + + lines = value.split(C_NEWLINE); + + trimmedLines = removeIndentation( + value, getIndent(max).indent + ).split(C_NEWLINE); + + /* + * We replaced the initial bullet with something + * else above, which was used to trick + * `removeIndentation` into removing some more + * characters when possible. However, that could + * result in the initial line to be stripped more + * than it should be. + */ + + trimmedLines[0] = rest; + + indent(bullet.length); + + index = 0; + length = lines.length; + + while (++index < length) { + indent(lines[index].length - trimmedLines[index].length); + } + + return trimmedLines.join(C_NEWLINE); +} + +/** + * Create a list-item node. + * + * @example + * renderListItem('- _foo_', now()); + * + * @param {Object} value - List-item. + * @param {Object} position - List-item location. + * @return {Object} - `listItem` node. + */ +function renderListItem(value, position) { + var self = this; + var checked = null; + var node; + var task; + var indent; + + value = LIST_ITEM_MAP[self.options.pedantic].apply(self, arguments); + + if (self.options.gfm) { + task = value.match(EXPRESSION_TASK_ITEM); + + if (task) { + indent = task[0].length; + checked = task[1].toLowerCase() === C_X_LOWER; + + self.indent(position.line)(indent); + value = value.slice(indent); + } + } + + node = { + 'type': T_LIST_ITEM, + 'loose': EXPRESSION_LOOSE_LIST_ITEM.test(value) || + value.charAt(value.length - 1) === C_NEWLINE, + 'checked': checked + }; + + node.children = self.tokenizeBlock(value, position); + + return node; +} + +/** + * Create a footnote-definition node. + * + * @example + * renderFootnoteDefinition('1', '_foo_', now()); + * + * @param {string} identifier - Unique reference. + * @param {string} value - Contents + * @param {Object} position - Definition location. + * @return {Object} - `footnoteDefinition` node. + */ +function renderFootnoteDefinition(identifier, value, position) { + var self = this; + var exitBlockquote = self.enterBlock(); + var node; + + node = { + 'type': T_FOOTNOTE_DEFINITION, + 'identifier': identifier, + 'children': self.tokenizeBlock(value, position) + }; + + exitBlockquote(); + + return node; +} + +/** + * Create a heading node. + * + * @example + * renderHeading('_foo_', 1, now()); + * + * @param {string} value - Content. + * @param {number} depth - Heading depth. + * @param {Object} position - Heading content location. + * @return {Object} - `heading` node + */ +function renderHeading(value, depth, position) { + return { + 'type': T_HEADING, + 'depth': depth, + 'children': this.tokenizeInline(value, position) + }; +} + +/** + * Create a blockquote node. + * + * @example + * renderBlockquote('_foo_', eat); + * + * @param {string} value - Content. + * @param {Object} now - Position. + * @return {Object} - `blockquote` node. + */ +function renderBlockquote(value, now) { + var self = this; + var exitBlockquote = self.enterBlock(); + var node = { + 'type': T_BLOCKQUOTE, + 'children': self.tokenizeBlock(value, now) + }; + + exitBlockquote(); + + return node; +} + +/** + * Create a void node. + * + * @example + * renderVoid('thematicBreak'); + * + * @param {string} type - Node type. + * @return {Object} - Node of type `type`. + */ +function renderVoid(type) { + return { + 'type': type + }; +} + +/** + * Create a parent. + * + * @example + * renderParent('paragraph', '_foo_'); + * + * @param {string} type - Node type. + * @param {Array.<Object>} children - Child nodes. + * @return {Object} - Node of type `type`. + */ +function renderParent(type, children) { + return { + 'type': type, + 'children': children + }; +} + +/** + * Create a raw node. + * + * @example + * renderRaw('inlineCode', 'foo()'); + * + * @param {string} type - Node type. + * @param {string} value - Contents. + * @return {Object} - Node of type `type`. + */ +function renderRaw(type, value) { + return { + 'type': type, + 'value': value + }; +} + +/** + * Create a link node. + * + * @example + * renderLink(true, 'example.com', 'example', 'Example Domain', now(), eat); + * renderLink(false, 'fav.ico', 'example', 'Example Domain', now(), eat); + * + * @param {boolean} isLink - Whether linking to a document + * or an image. + * @param {string} url - URI reference. + * @param {string} content - Content. + * @param {string?} title - Title. + * @param {Object} position - Location of link. + * @return {Object} - `link` or `image` node. + */ +function renderLink(isLink, url, content, title, position) { + var self = this; + var exitLink = self.enterLink(); + var node; + + node = { + 'type': isLink ? T_LINK : T_IMAGE, + 'title': title || null + }; + + if (isLink) { + node.url = url; + node.children = self.tokenizeInline(content, position); + } else { + node.url = url; + node.alt = content ? + self.decode.raw(self.descape(content), position) : + null; + } + + exitLink(); + + return node; +} + +/** + * Create a footnote node. + * + * @example + * renderFootnote('_foo_', now()); + * + * @param {string} value - Contents. + * @param {Object} position - Location of footnote. + * @return {Object} - `footnote` node. + */ +function renderFootnote(value, position) { + return this.renderInline(T_FOOTNOTE, value, position); +} + +/** + * Add a node with inline content. + * + * @example + * renderInline('strong', '_foo_', now()); + * + * @param {string} type - Node type. + * @param {string} value - Contents. + * @param {Object} position - Location of node. + * @return {Object} - Node of type `type`. + */ +function renderInline(type, value, position) { + return this.renderParent(type, this.tokenizeInline(value, position)); +} + +/** + * Add a node with block content. + * + * @example + * renderBlock('blockquote', 'Foo.', now()); + * + * @param {string} type - Node type. + * @param {string} value - Contents. + * @param {Object} position - Location of node. + * @return {Object} - Node of type `type`. + */ +function renderBlock(type, value, position) { + return this.renderParent(type, this.tokenizeBlock(value, position)); +} + +/** + * Find a possible escape sequence. + * + * @example + * locateEscape('foo \- bar'); // 4 + * + * @param {string} value - Value to search. + * @param {number} fromIndex - Index to start searching at. + * @return {number} - Location of possible escape sequence. + */ +function locateEscape(value, fromIndex) { + return value.indexOf(C_BACKSLASH, fromIndex); +} + +/** + * Tokenise an escape sequence. + * + * @example + * tokenizeEscape(eat, '\\a'); + * + * @property {Function} locator - Escape locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `text` or `break` node. + */ +function tokenizeEscape(eat, value, silent) { + var self = this; + var character; + + if (value.charAt(0) === C_BACKSLASH) { + character = value.charAt(1); + + if (self.escape.indexOf(character) !== -1) { + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + return eat(C_BACKSLASH + character)( + character === C_NEWLINE ? + self.renderVoid(T_BREAK) : + self.renderRaw(T_TEXT, character) + ); + } + } +} + +tokenizeEscape.locator = locateEscape; + +/** + * Find a possible auto-link. + * + * @example + * locateAutoLink('foo <bar'); // 4 + * + * @param {string} value - Value to search. + * @param {number} fromIndex - Index to start searching at. + * @return {number} - Location of possible auto-link. + */ +function locateAutoLink(value, fromIndex) { + return value.indexOf(C_LT, fromIndex); +} + +/** + * Tokenise a URL in carets. + * + * @example + * tokenizeAutoLink(eat, '<http://foo.bar>'); + * + * @property {boolean} notInLink + * @property {Function} locator - Auto-link locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `link` node. + */ +function tokenizeAutoLink(eat, value, silent) { + var self; + var subvalue; + var length; + var index; + var queue; + var character; + var hasAtCharacter; + var link; + var now; + var content; + var tokenize; + var node; + + if (value.charAt(0) !== C_LT) { + return; + } + + self = this; + subvalue = EMPTY; + length = value.length; + index = 0; + queue = EMPTY; + hasAtCharacter = false; + link = EMPTY; + + index++; + subvalue = C_LT; + + while (index < length) { + character = value.charAt(index); + + if ( + character === C_SPACE || + character === C_GT || + character === C_AT_SIGN || + (character === C_COLON && value.charAt(index + 1) === C_SLASH) + ) { + break; + } + + queue += character; + index++; + } + + if (!queue) { + return; + } + + link += queue; + queue = EMPTY; + + character = value.charAt(index); + link += character; + index++; + + if (character === C_AT_SIGN) { + hasAtCharacter = true; + } else { + if ( + character !== C_COLON || + value.charAt(index + 1) !== C_SLASH + ) { + return; + } + + link += C_SLASH; + index++; + } + + while (index < length) { + character = value.charAt(index); + + if (character === C_SPACE || character === C_GT) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + + if (!queue || character !== C_GT) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + link += queue; + content = link; + subvalue += link + character; + now = eat.now(); + now.column++; + now.offset++; + + if (hasAtCharacter) { + if ( + link.substr(0, MAILTO_PROTOCOL.length).toLowerCase() !== + MAILTO_PROTOCOL + ) { + link = MAILTO_PROTOCOL + link; + } else { + content = content.substr(MAILTO_PROTOCOL.length); + now.column += MAILTO_PROTOCOL.length; + now.offset += MAILTO_PROTOCOL.length; + } + } + + /* + * Temporarily remove support for escapes in autolinks. + */ + + tokenize = self.inlineTokenizers.escape; + self.inlineTokenizers.escape = null; + + node = eat(subvalue)( + self.renderLink(true, decode(link), content, null, now, eat) + ); + + self.inlineTokenizers.escape = tokenize; + + return node; +} + +tokenizeAutoLink.notInLink = true; +tokenizeAutoLink.locator = locateAutoLink; + +/** + * Find a possible URL. + * + * @example + * locateURL('foo http://bar'); // 4 + * + * @param {string} value - Value to search. + * @param {number} fromIndex - Index to start searching at. + * @return {number} - Location of possible URL. + */ +function locateURL(value, fromIndex) { + var index = -1; + var min = -1; + var position; + + if (!this.options.gfm) { + return -1; + } + + while (++index < PROTOCOLS_LENGTH) { + position = value.indexOf(PROTOCOLS[index], fromIndex); + + if (position !== -1 && (position < min || min === -1)) { + min = position; + } + } + + return min; +} + +/** + * Tokenise a URL in text. + * + * @example + * tokenizeURL(eat, 'http://foo.bar'); + * + * @property {boolean} notInLink + * @property {Function} locator - URL locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `link` node. + */ +function tokenizeURL(eat, value, silent) { + var self = this; + var subvalue; + var content; + var character; + var index; + var position; + var protocol; + var match; + var length; + var queue; + var parenCount; + var nextCharacter; + var now; + + if (!self.options.gfm) { + return; + } + + subvalue = EMPTY; + index = -1; + length = PROTOCOLS_LENGTH; + + while (++index < length) { + protocol = PROTOCOLS[index]; + match = value.slice(0, protocol.length); + + if (match.toLowerCase() === protocol) { + subvalue = match; + break; + } + } + + if (!subvalue) { + return; + } + + index = subvalue.length; + length = value.length; + queue = EMPTY; + parenCount = 0; + + while (index < length) { + character = value.charAt(index); + + if (isWhiteSpace(character) || character === C_LT) { + break; + } + + if ( + character === C_DOT || + character === C_COMMA || + character === C_COLON || + character === C_SEMI_COLON || + character === C_DOUBLE_QUOTE || + character === C_SINGLE_QUOTE || + character === C_PAREN_CLOSE || + character === C_BRACKET_CLOSE + ) { + nextCharacter = value.charAt(index + 1); + + if ( + !nextCharacter || + isWhiteSpace(nextCharacter) + ) { + break; + } + } + + if ( + character === C_PAREN_OPEN || + character === C_BRACKET_OPEN + ) { + parenCount++; + } + + if ( + character === C_PAREN_CLOSE || + character === C_BRACKET_CLOSE + ) { + parenCount--; + + if (parenCount < 0) { + break; + } + } + + queue += character; + index++; + } + + if (!queue) { + return; + } + + subvalue += queue; + content = subvalue; + + if (protocol === MAILTO_PROTOCOL) { + position = queue.indexOf(C_AT_SIGN); + + if (position === -1 || position === length - 1) { + return; + } + + content = content.substr(MAILTO_PROTOCOL.length); + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + now = eat.now(); + + return eat(subvalue)( + self.renderLink(true, decode(subvalue), content, null, now, eat) + ); +} + +tokenizeURL.notInLink = true; +tokenizeURL.locator = locateURL; + +/** + * Find a possible tag. + * + * @example + * locateTag('foo <bar'); // 4 + * + * @param {string} value - Value to search. + * @param {number} fromIndex - Index to start searching at. + * @return {number} - Location of possible tag. + */ +function locateTag(value, fromIndex) { + return value.indexOf(C_LT, fromIndex); +} + +/** + * Tokenise an HTML tag. + * + * @example + * tokenizeInlineHTML(eat, '<span foo="bar">'); + * + * @property {Function} locator - Tag locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `html` node. + */ +function tokenizeInlineHTML(eat, value, silent) { + var self = this; + var subvalue = eatHTMLComment(value, self.options) || + eatHTMLCDATA(value) || + eatHTMLProcessingInstruction(value) || + eatHTMLDeclaration(value) || + eatHTMLClosingTag(value) || + eatHTMLOpeningTag(value); + + if (!subvalue) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + if (!self.inLink && EXPRESSION_HTML_LINK_OPEN.test(subvalue)) { + self.inLink = true; + } else if (self.inLink && EXPRESSION_HTML_LINK_CLOSE.test(subvalue)) { + self.inLink = false; + } + + return eat(subvalue)(self.renderRaw(T_HTML, subvalue)); +} + +tokenizeInlineHTML.locator = locateTag; + +/** + * Find a possible link. + * + * @example + * locateLink('foo ![bar'); // 4 + * + * @param {string} value - Value to search. + * @param {number} fromIndex - Index to start searching at. + * @return {number} - Location of possible link. + */ +function locateLink(value, fromIndex) { + var link = value.indexOf(C_BRACKET_OPEN, fromIndex); + var image = value.indexOf(C_EXCLAMATION_MARK + C_BRACKET_OPEN, fromIndex); + + if (image === -1) { + return link; + } + + /* + * Link can never be `-1` if an image is found, so we don’t need to + * check for that :) + */ + + return link < image ? link : image; +} + +/** + * Tokenise a link. + * + * @example + * tokenizeLink(eat, '![foo](fav.ico "Favicon")); + * + * @property {Function} locator - Link locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `link` or `image` node. + */ +function tokenizeLink(eat, value, silent) { + var self = this; + var subvalue = EMPTY; + var index = 0; + var character = value.charAt(0); + var commonmark = self.options.commonmark; + var gfm = self.options.gfm; + var closed; + var count; + var opening; + var beforeURL; + var beforeTitle; + var subqueue; + var openCount; + var hasMarker; + var markers; + var isImage; + var content; + var marker; + var length; + var title; + var depth; + var queue; + var url; + var now; + + /* + * Detect whether this is an image. + */ + + if (character === C_EXCLAMATION_MARK) { + isImage = true; + subvalue = character; + character = value.charAt(++index); + } + + /* + * Eat the opening. + */ + + if (character !== C_BRACKET_OPEN) { + return; + } + + /* + * Exit when this is a link and we’re already inside + * a link. + */ + + if (!isImage && self.inLink) { + return; + } + + subvalue += character; + queue = EMPTY; + index++; + + /* + * Eat the content. + */ + + length = value.length; + now = eat.now(); + depth = 0; + + now.column += index; + now.offset += index; + + while (index < length) { + subqueue = character = value.charAt(index); + + if (character === C_TICK) { + /* Inline-code in link content. */ + count = 1; + + while (value.charAt(index + 1) === C_TICK) { + subqueue += character; + index++; + count++; + } + + if (!opening) { + opening = count; + } else if (count >= opening) { + opening = 0; + } + } else if (character === C_BACKSLASH) { + /* Allow brackets to be escaped. */ + index++; + subqueue += value.charAt(index); + /* In GFM mode, brackets in code still count. + * In all other modes, they don’t. This empty + * block prevents the next statements are + * entered. */ + } else if ((!opening || gfm) && character === C_BRACKET_OPEN) { + depth++; + } else if ((!opening || gfm) && character === C_BRACKET_CLOSE) { + if (depth) { + depth--; + } else { + /* Allow white-space between content and + * url in GFM mode. */ + if (gfm) { + while (index < length) { + character = value.charAt(index + 1); + + if (!isWhiteSpace(character)) { + break; + } + + subqueue += character; + index++; + } + } + + if (value.charAt(index + 1) !== C_PAREN_OPEN) { + return; + } + + subqueue += C_PAREN_OPEN; + closed = true; + index++; + + break; + } + } + + queue += subqueue; + subqueue = EMPTY; + index++; + } + + /* Eat the content closing. */ + if (!closed) { + return; + } + + content = queue; + subvalue += queue + subqueue; + index++; + + /* + * Eat white-space. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + subvalue += character; + index++; + } + + /* + * Eat the URL. + */ + + character = value.charAt(index); + markers = commonmark ? COMMONMARK_LINK_MARKERS : LINK_MARKERS; + openCount = 0; + queue = EMPTY; + beforeURL = subvalue; + + if (character === C_LT) { + index++; + beforeURL += C_LT; + + while (index < length) { + character = value.charAt(index); + + if (character === C_GT) { + break; + } + + if (commonmark && character === C_NEWLINE) { + return; + } + + queue += character; + index++; + } + + if (value.charAt(index) !== C_GT) { + return; + } + + subvalue += C_LT + queue + C_GT; + url = queue; + index++; + } else { + character = null; + subqueue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (subqueue && has.call(markers, character)) { + break; + } + + if (isWhiteSpace(character)) { + if (commonmark) { + break; + } + + subqueue += character; + } else { + if (character === C_PAREN_OPEN) { + depth++; + openCount++; + } else if (character === C_PAREN_CLOSE) { + if (depth === 0) { + break; + } + + depth--; + } + + queue += subqueue; + subqueue = EMPTY; + + if (character === C_BACKSLASH) { + queue += C_BACKSLASH; + character = value.charAt(++index); + } + + queue += character; + } + + index++; + } + + subvalue += queue; + url = queue; + index = subvalue.length; + } + + /* + * Eat white-space. + */ + + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + subvalue += queue; + + /* + * Eat the title. + */ + + if (queue && has.call(markers, character)) { + index++; + subvalue += character; + queue = EMPTY; + marker = markers[character]; + beforeTitle = subvalue; + + /* + * In commonmark-mode, things are pretty easy: the + * marker cannot occur inside the title. + * + * Non-commonmark does, however, support nested + * delimiters. + */ + + if (commonmark) { + while (index < length) { + character = value.charAt(index); + + if (character === marker) { + break; + } + + if (character === C_BACKSLASH) { + queue += C_BACKSLASH; + character = value.charAt(++index); + } + + index++; + queue += character; + } + + character = value.charAt(index); + + if (character !== marker) { + return; + } + + title = queue; + subvalue += queue + character; + index++; + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + subvalue += character; + index++; + } + } else { + subqueue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === marker) { + if (hasMarker) { + queue += marker + subqueue; + subqueue = EMPTY; + } + + hasMarker = true; + } else if (!hasMarker) { + queue += character; + } else if (character === C_PAREN_CLOSE) { + subvalue += queue + marker + subqueue; + title = queue; + break; + } else if (isWhiteSpace(character)) { + subqueue += character; + } else { + queue += marker + subqueue + character; + subqueue = EMPTY; + hasMarker = false; + } + + index++; + } + } + } + + if (value.charAt(index) !== C_PAREN_CLOSE) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + subvalue += C_PAREN_CLOSE; + + url = self.decode.raw(self.descape(url), eat(beforeURL).test().end); + + if (title) { + beforeTitle = eat(beforeTitle).test().end; + title = self.decode.raw(self.descape(title), beforeTitle); + } + + return eat(subvalue)( + self.renderLink(!isImage, url, content, title, now, eat) + ); +} + +tokenizeLink.locator = locateLink; + +/** + * Tokenise a reference link, image, or footnote; + * shortcut reference link, or footnote. + * + * @example + * tokenizeReference(eat, '[foo]'); + * tokenizeReference(eat, '[foo][]'); + * tokenizeReference(eat, '[foo][bar]'); + * + * @property {Function} locator - Reference locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - Reference node. + */ +function tokenizeReference(eat, value, silent) { + var self = this; + var character = value.charAt(0); + var index = 0; + var length = value.length; + var subvalue = EMPTY; + var intro = EMPTY; + var type = T_LINK; + var referenceType = REFERENCE_TYPE_SHORTCUT; + var content; + var identifier; + var now; + var node; + var exitLink; + var queue; + var bracketed; + var depth; + + /* + * Check whether we’re eating an image. + */ + + if (character === C_EXCLAMATION_MARK) { + type = T_IMAGE; + intro = character; + character = value.charAt(++index); + } + + if (character !== C_BRACKET_OPEN) { + return; + } + + index++; + intro += character; + queue = EMPTY; + + /* + * Check whether we’re eating a footnote. + */ + + if ( + self.options.footnotes && + type === T_LINK && + value.charAt(index) === C_CARET + ) { + intro += C_CARET; + index++; + type = T_FOOTNOTE; + } + + /* + * Eat the text. + */ + + depth = 0; + + while (index < length) { + character = value.charAt(index); + + if (character === C_BRACKET_OPEN) { + bracketed = true; + depth++; + } else if (character === C_BRACKET_CLOSE) { + if (!depth) { + break; + } + + depth--; + } + + if (character === C_BACKSLASH) { + queue += C_BACKSLASH; + character = value.charAt(++index); + } + + queue += character; + index++; + } + + subvalue = content = queue; + character = value.charAt(index); + + if (character !== C_BRACKET_CLOSE) { + return; + } + + index++; + subvalue += character; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + + if (character !== C_BRACKET_OPEN) { + if (!content) { + return; + } + + identifier = content; + } else { + identifier = EMPTY; + queue += character; + index++; + + while (index < length) { + character = value.charAt(index); + + if ( + character === C_BRACKET_OPEN || + character === C_BRACKET_CLOSE + ) { + break; + } + + if (character === C_BACKSLASH) { + identifier += C_BACKSLASH; + character = value.charAt(++index); + } + + identifier += character; + index++; + } + + character = value.charAt(index); + + if (character === C_BRACKET_CLOSE) { + queue += identifier + character; + index++; + + referenceType = identifier ? + REFERENCE_TYPE_FULL : + REFERENCE_TYPE_COLLAPSED; + } else { + identifier = EMPTY; + } + + subvalue += queue; + queue = EMPTY; + } + + /* + * Brackets cannot be inside the identifier. + */ + + if (referenceType !== REFERENCE_TYPE_FULL && bracketed) { + return; + } + + /* + * Inline footnotes cannot have an identifier. + */ + + if (type === T_FOOTNOTE && referenceType !== REFERENCE_TYPE_SHORTCUT) { + type = T_LINK; + intro = C_BRACKET_OPEN + C_CARET; + content = C_CARET + content; + } + + subvalue = intro + subvalue; + + if (type === T_LINK && self.inLink) { + return null; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + if (type === T_FOOTNOTE && content.indexOf(C_SPACE) !== -1) { + return eat(subvalue)(self.renderFootnote(content, eat.now())); + } + + now = eat.now(); + now.column += intro.length; + now.offset += intro.length; + identifier = referenceType === REFERENCE_TYPE_FULL ? identifier : content; + + node = { + 'type': type + 'Reference', + 'identifier': normalize(identifier) + }; + + if (type === T_LINK || type === T_IMAGE) { + node.referenceType = referenceType; + } + + if (type === T_LINK) { + exitLink = self.enterLink(); + node.children = self.tokenizeInline(content, now); + exitLink(); + } else if (type === T_IMAGE) { + node.alt = self.decode.raw(self.descape(content), now) || null; + } + + return eat(subvalue)(node); +} + +tokenizeReference.locator = locateLink; + +/** + * Find a possible strong emphasis. + * + * @example + * locateStrong('foo **bar'); // 4 + * + * @param {string} value - Value to search. + * @param {number} fromIndex - Index to start searching at. + * @return {number} - Location of possible strong emphasis. + */ +function locateStrong(value, fromIndex) { + var asterisk = value.indexOf(C_ASTERISK + C_ASTERISK, fromIndex); + var underscore = value.indexOf(C_UNDERSCORE + C_UNDERSCORE, fromIndex); + + if (underscore === -1) { + return asterisk; + } + + if (asterisk === -1) { + return underscore; + } + + return underscore < asterisk ? underscore : asterisk; +} + +/** + * Tokenise strong emphasis. + * + * @example + * tokenizeStrong(eat, '**foo**'); + * tokenizeStrong(eat, '__foo__'); + * + * @property {Function} locator - Strong emphasis locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `strong` node. + */ +function tokenizeStrong(eat, value, silent) { + var self = this; + var index = 0; + var character = value.charAt(index); + var now; + var pedantic; + var marker; + var queue; + var subvalue; + var length; + var prev; + + if ( + EMPHASIS_MARKERS[character] !== true || + value.charAt(++index) !== character + ) { + return; + } + + pedantic = self.options.pedantic; + marker = character; + subvalue = marker + marker; + length = value.length; + index++; + queue = character = EMPTY; + + if (pedantic && isWhiteSpace(value.charAt(index))) { + return; + } + + while (index < length) { + prev = character; + character = value.charAt(index); + + if ( + character === marker && + value.charAt(index + 1) === marker && + (!pedantic || !isWhiteSpace(prev)) + ) { + character = value.charAt(index + 2); + + if (character !== marker) { + if (!trim(queue)) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + now = eat.now(); + now.column += 2; + now.offset += 2; + + return eat(subvalue + queue + subvalue)( + self.renderInline(T_STRONG, queue, now) + ); + } + } + + if (!pedantic && character === C_BACKSLASH) { + queue += character; + character = value.charAt(++index); + } + + queue += character; + index++; + } +} + +tokenizeStrong.locator = locateStrong; + +/** + * Find possible slight emphasis. + * + * @example + * locateEmphasis('foo *bar'); // 4 + * + * @param {string} value - Value to search. + * @param {number} fromIndex - Index to start searching at. + * @return {number} - Location of possible slight emphasis. + */ +function locateEmphasis(value, fromIndex) { + var asterisk = value.indexOf(C_ASTERISK, fromIndex); + var underscore = value.indexOf(C_UNDERSCORE, fromIndex); + + if (underscore === -1) { + return asterisk; + } + + if (asterisk === -1) { + return underscore; + } + + return underscore < asterisk ? underscore : asterisk; +} + +/** + * Tokenise slight emphasis. + * + * @example + * tokenizeEmphasis(eat, '*foo*'); + * tokenizeEmphasis(eat, '_foo_'); + * + * @property {Function} locator - Slight emphasis locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `emphasis` node. + */ +function tokenizeEmphasis(eat, value, silent) { + var self = this; + var index = 0; + var character = value.charAt(index); + var now; + var pedantic; + var marker; + var queue; + var subvalue; + var length; + var prev; + + if (EMPHASIS_MARKERS[character] !== true) { + return; + } + + pedantic = self.options.pedantic; + subvalue = marker = character; + length = value.length; + index++; + queue = character = EMPTY; + + if (pedantic && isWhiteSpace(value.charAt(index))) { + return; + } + + while (index < length) { + prev = character; + character = value.charAt(index); + + if ( + character === marker && + (!pedantic || !isWhiteSpace(prev)) + ) { + character = value.charAt(++index); + + if (character !== marker) { + if (!trim(queue) || prev === marker) { + return; + } + + if ( + pedantic || + marker !== C_UNDERSCORE || + !isWordCharacter(character) + ) { + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + now = eat.now(); + now.column++; + now.offset++; + + return eat(subvalue + queue + marker)( + self.renderInline(T_EMPHASIS, queue, now) + ); + } + } + + queue += marker; + } + + if (!pedantic && character === C_BACKSLASH) { + queue += character; + character = value.charAt(++index); + } + + queue += character; + index++; + } +} + +tokenizeEmphasis.locator = locateEmphasis; + +/** + * Find a possible deletion. + * + * @example + * locateDeletion('foo ~~bar'); // 4 + * + * @param {string} value - Value to search. + * @param {number} fromIndex - Index to start searching at. + * @return {number} - Location of possible deletion. + */ +function locateDeletion(value, fromIndex) { + return value.indexOf(C_TILDE + C_TILDE, fromIndex); +} + +/** + * Tokenise a deletion. + * + * @example + * tokenizeDeletion(eat, '~~foo~~'); + * + * @property {Function} locator - Deletion locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `delete` node. + */ +function tokenizeDeletion(eat, value, silent) { + var self = this; + var character = EMPTY; + var previous = EMPTY; + var preceding = EMPTY; + var subvalue = EMPTY; + var index; + var length; + var now; + + if ( + !self.options.gfm || + value.charAt(0) !== C_TILDE || + value.charAt(1) !== C_TILDE || + isWhiteSpace(value.charAt(2)) + ) { + return; + } + + index = 1; + length = value.length; + now = eat.now(); + now.column += 2; + now.offset += 2; + + while (++index < length) { + character = value.charAt(index); + + if ( + character === C_TILDE && + previous === C_TILDE && + (!preceding || !isWhiteSpace(preceding)) + ) { + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + return eat(C_TILDE + C_TILDE + subvalue + C_TILDE + C_TILDE)( + self.renderInline(T_DELETE, subvalue, now) + ); + } + + subvalue += previous; + preceding = previous; + previous = character; + } +} + +tokenizeDeletion.locator = locateDeletion; + +/** + * Find possible inline code. + * + * @example + * locateInlineCode('foo `bar'); // 4 + * + * @param {string} value - Value to search. + * @param {number} fromIndex - Index to start searching at. + * @return {number} - Location of possible inline code. + */ +function locateInlineCode(value, fromIndex) { + return value.indexOf(C_TICK, fromIndex); +} + +/** + * Tokenise inline code. + * + * @example + * tokenizeInlineCode(eat, '`foo()`'); + * + * @property {Function} locator - Inline code locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `inlineCode` node. + */ +function tokenizeInlineCode(eat, value, silent) { + var self = this; + var length = value.length; + var index = 0; + var queue = EMPTY; + var tickQueue = EMPTY; + var contentQueue; + var subqueue; + var count; + var openingCount; + var subvalue; + var character; + var found; + var next; + + while (index < length) { + if (value.charAt(index) !== C_TICK) { + break; + } + + queue += C_TICK; + index++; + } + + if (!queue) { + return; + } + + subvalue = queue; + openingCount = index; + queue = EMPTY; + next = value.charAt(index); + count = 0; + + while (index < length) { + character = next; + next = value.charAt(index + 1); + + if (character === C_TICK) { + count++; + tickQueue += character; + } else { + count = 0; + queue += character; + } + + if (count && next !== C_TICK) { + if (count === openingCount) { + subvalue += queue + tickQueue; + found = true; + break; + } + + queue += tickQueue; + tickQueue = EMPTY; + } + + index++; + } + + if (!found) { + if (openingCount % 2 !== 0) { + return; + } + + queue = EMPTY; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + contentQueue = subqueue = EMPTY; + length = queue.length; + index = -1; + + while (++index < length) { + character = queue.charAt(index); + + if (isWhiteSpace(character)) { + subqueue += character; + continue; + } + + if (subqueue) { + if (contentQueue) { + contentQueue += subqueue; + } + + subqueue = EMPTY; + } + + contentQueue += character; + } + + return eat(subvalue)(self.renderRaw(T_INLINE_CODE, contentQueue)); +} + +tokenizeInlineCode.locator = locateInlineCode; + +/** + * Find a possible break. + * + * @example + * locateBreak('foo \nbar'); // 3 + * + * @param {string} value - Value to search. + * @param {number} fromIndex - Index to start searching at. + * @return {number} - Location of possible break. + */ +function locateBreak(value, fromIndex) { + var index = value.indexOf(C_NEWLINE, fromIndex); + + while (index > fromIndex) { + if (value.charAt(index - 1) !== C_SPACE) { + break; + } + + index--; + } + + return index; +} + +/** + * Tokenise a break. + * + * @example + * tokenizeBreak(eat, ' \n'); + * + * @property {Function} locator - Break locator. + * @param {function(string)} eat - Eater. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `break` node. + */ +function tokenizeBreak(eat, value, silent) { + var self = this; + var breaks = self.options.breaks; + var length = value.length; + var index = -1; + var queue = EMPTY; + var character; + + while (++index < length) { + character = value.charAt(index); + + if (character === C_NEWLINE) { + if (!breaks && index < MIN_BREAK_LENGTH) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + queue += character; + return eat(queue)(self.renderVoid(T_BREAK)); + } + + if (character !== C_SPACE) { + return; + } + + queue += character; + } +} + +tokenizeBreak.locator = locateBreak; + +/** + * Construct a new parser. + * + * @example + * var parser = new Parser(new VFile('Foo')); + * + * @constructor + * @class {Parser} + * @param {VFile} file - File to parse. + * @param {Object?} [options] - Passed to + * `Parser#setOptions()`. + */ +function Parser(file, options) { + var self = this; + + self.file = file; + self.inLink = false; + self.inBlock = false; + self.inList = false; + self.atStart = true; + self.toOffset = vfileLocation(file).toOffset; + + self.descape = descapeFactory(self, 'escape'); + self.decode = decodeFactory(self); + + self.options = extend({}, self.options); + + self.setOptions(options); +} + +/** + * Set options. Does not overwrite previously set + * options. + * + * @example + * var parser = new Parser(); + * parser.setOptions({gfm: true}); + * + * @this {Parser} + * @throws {Error} - When an option is invalid. + * @param {Object?} [options] - Parse settings. + * @return {Parser} - `self`. + */ +Parser.prototype.setOptions = function (options) { + var self = this; + var current = self.options; + var key; + + if (options === null || options === undefined) { + options = {}; + } else if (typeof options === 'object') { + options = extend({}, options); + } else { + throw new Error( + 'Invalid value `' + options + '` ' + + 'for setting `options`' + ); + } + + for (key in defaultOptions) { + var value = options[key]; + + if (value === null || value === undefined) { + value = current[key]; + } + + if (typeof value !== 'boolean') { + throw new Error( + 'Invalid value `' + value + '` ' + + 'for setting `options.' + key + '`' + ); + } + + options[key] = value; + } + + self.options = options; + + if (options.commonmark) { + self.escape = escapes.commonmark; + } else if (options.gfm) { + self.escape = escapes.gfm; + } else { + self.escape = escapes.default; + } + + return self; +}; + +/* + * Expose `defaults`. + */ + +Parser.prototype.options = defaultOptions; + +/** + * Factory to track indentation for each line corresponding + * to the given `start` and the number of invocations. + * + * @param {number} start - Starting line. + * @return {function(offset)} - Indenter. + */ +Parser.prototype.indent = function (start) { + var self = this; + var line = start; + + /** + * Intender which increments the global offset, + * starting at the bound line, and further incrementing + * each line for each invocation. + * + * @example + * indenter(2); + * + * @param {number} offset - Number to increment the + * offset. + */ + function indenter(offset) { + self.offset[line] = (self.offset[line] || 0) + offset; + + line++; + } + + return indenter; +}; + +/** + * Get found offsets starting at `start`. + * + * @param {number} start - Starting line. + * @return {Array.<number>} - Offsets starting at `start`. + */ +Parser.prototype.getIndent = function (start) { + var offset = this.offset; + var result = []; + + while (++start) { + if (!(start in offset)) { + break; + } + + result.push((offset[start] || 0) + 1); + } + + return result; +}; + +/** + * Parse the bound file. + * + * @example + * new Parser(new File('_Foo_.')).parse(); + * + * @this {Parser} + * @return {Object} - `root` node. + */ +Parser.prototype.parse = function () { + var self = this; + var value = String(self.file); + var column = 1; + var node; + + /* + * Clean non-unix newlines: `\r\n` and `\r` are all + * changed to `\n`. This should not affect positional + * information. + */ + + value = value.replace(EXPRESSION_LINE_BREAKS, C_NEWLINE); + + if (value.charCodeAt(0) === 0xFEFF) { + value = value.slice(1); + column++; + self.offset++; + } + + /* + * Add an `offset` matrix, used to keep track of + * syntax and white space indentation per line. + */ + + self.offset = {}; + + node = self.renderBlock(T_ROOT, value, { + 'line': 1, + 'column': column + }); + + node.position = { + 'start': { + 'line': 1, + 'column': 1, + 'offset': 0 + } + }; + + node.position.end = self.eof || extend({}, node.position.start); + + if (!self.options.position) { + removePosition(node); + } + + return node; +}; + +/* + * Enter and exit helpers. + */ + +Parser.prototype.exitStart = toggle('atStart', true); +Parser.prototype.enterList = toggle('inList', false); +Parser.prototype.enterLink = toggle('inLink', false); +Parser.prototype.enterBlock = toggle('inBlock', false); + +/* + * Expose helpers + */ + +Parser.prototype.renderRaw = renderRaw; +Parser.prototype.renderVoid = renderVoid; +Parser.prototype.renderParent = renderParent; +Parser.prototype.renderInline = renderInline; +Parser.prototype.renderBlock = renderBlock; + +Parser.prototype.renderLink = renderLink; +Parser.prototype.renderCodeBlock = renderCodeBlock; +Parser.prototype.renderBlockquote = renderBlockquote; +Parser.prototype.renderListItem = renderListItem; +Parser.prototype.renderFootnoteDefinition = renderFootnoteDefinition; +Parser.prototype.renderHeading = renderHeading; +Parser.prototype.renderFootnote = renderFootnote; + +/** + * Construct a tokenizer. This creates both + * `tokenizeInline` and `tokenizeBlock`. + * + * @example + * Parser.prototype.tokenizeInline = tokenizeFactory('inline'); + * + * @param {string} type - Name of parser, used to find + * its expressions (`%sMethods`) and tokenizers + * (`%Tokenizers`). + * @return {Function} - Tokenizer. + */ +function tokenizeFactory(type) { + /** + * Tokenizer for a bound `type` + * + * @example + * parser = new Parser(); + * parser.tokenizeInline('_foo_'); + * + * @param {string} value - Content. + * @param {Object} location - Offset at which `value` + * starts. + * @return {Array.<Object>} - Nodes. + */ + function tokenize(value, location) { + var self = this; + var offset = self.offset; + var tokens = []; + var methods = self[type + 'Methods']; + var tokenizers = self[type + 'Tokenizers']; + var line = location.line; + var column = location.column; + var add; + var index; + var length; + var method; + var name; + var matched; + var valueLength; + + /* + * Trim white space only lines. + */ + + if (!value) { + return tokens; + } + + /** + * Update line, column, and offset based on + * `value`. + * + * @example + * updatePosition('foo'); + * + * @param {string} subvalue - Subvalue to eat. + */ + function updatePosition(subvalue) { + var lastIndex = -1; + var index = subvalue.indexOf(C_NEWLINE); + + while (index !== -1) { + line++; + lastIndex = index; + index = subvalue.indexOf(C_NEWLINE, index + 1); + } + + if (lastIndex === -1) { + column += subvalue.length; + } else { + column = subvalue.length - lastIndex; + } + + if (line in offset) { + if (lastIndex !== -1) { + column += offset[line]; + } else if (column <= offset[line]) { + column = offset[line] + 1; + } + } + } + + /** + * Get offset. Called before the first character is + * eaten to retrieve the range's offsets. + * + * @return {Function} - `done`, to be called when + * the last character is eaten. + */ + function getOffset() { + var indentation = []; + var pos = line + 1; + + /** + * Done. Called when the last character is + * eaten to retrieve the range’s offsets. + * + * @return {Array.<number>} - Offset. + */ + function done() { + var last = line + 1; + + while (pos < last) { + indentation.push((offset[pos] || 0) + 1); + + pos++; + } + + return indentation; + } + + return done; + } + + /** + * Get the current position. + * + * @example + * position = now(); // {line: 1, column: 1, offset: 0} + * + * @return {Object} - Current Position. + */ + function now() { + var pos = { + 'line': line, + 'column': column + }; + + pos.offset = self.toOffset(pos); + + return pos; + } + + /** + * Store position information for a node. + * + * @example + * start = now(); + * updatePosition('foo'); + * location = new Position(start); + * // { + * // start: {line: 1, column: 1, offset: 0}, + * // end: {line: 1, column: 3, offset: 2} + * // } + * + * @param {Object} start - Starting position. + */ + function Position(start) { + this.start = start; + this.end = now(); + } + + /** + * Throw when a value is incorrectly eaten. + * This shouldn’t happen but will throw on new, + * incorrect rules. + * + * @example + * // When the current value is set to `foo bar`. + * validateEat('foo'); + * eat('foo'); + * + * validateEat('bar'); + * // throws, because the space is not eaten. + * + * @param {string} subvalue - Value to be eaten. + * @throws {Error} - When `subvalue` cannot be eaten. + */ + function validateEat(subvalue) { + /* istanbul ignore if */ + if (value.substring(0, subvalue.length) !== subvalue) { + self.file.fail(ERR_INCORRECTLY_EATEN, now()); + } + } + + /** + * Mark position and patch `node.position`. + * + * @example + * var update = position(); + * updatePosition('foo'); + * update({}); + * // { + * // position: { + * // start: {line: 1, column: 1, offset: 0}, + * // end: {line: 1, column: 3, offset: 2} + * // } + * // } + * + * @returns {Function} - Updater. + */ + function position() { + var before = now(); + + /** + * Add the position to a node. + * + * @example + * update({type: 'text', value: 'foo'}); + * + * @param {Node} node - Node to attach position + * on. + * @param {Array} [indent] - Indentation for + * `node`. + * @return {Node} - `node`. + */ + function update(node, indent) { + var prev = node.position; + var start = prev ? prev.start : before; + var combined = []; + var n = prev && prev.end.line; + var l = before.line; + + node.position = new Position(start); + + /* + * If there was already a `position`, this + * node was merged. Fixing `start` wasn’t + * hard, but the indent is different. + * Especially because some information, the + * indent between `n` and `l` wasn’t + * tracked. Luckily, that space is + * (should be?) empty, so we can safely + * check for it now. + */ + + if (prev && indent && prev.indent) { + combined = prev.indent; + + if (n < l) { + while (++n < l) { + combined.push((offset[n] || 0) + 1); + } + + combined.push(before.column); + } + + indent = combined.concat(indent); + } + + node.position.indent = indent || []; + + return node; + } + + return update; + } + + /** + * Add `node` to `parent`s children or to `tokens`. + * Performs merges where possible. + * + * @example + * add({}); + * + * add({}, {children: []}); + * + * @param {Object} node - Node to add. + * @param {Object} [parent] - Parent to insert into. + * @return {Object} - Added or merged into node. + */ + add = function (node, parent) { + var prev; + var children; + + if (!parent) { + children = tokens; + } else { + children = parent.children; + } + + prev = children[children.length - 1]; + + if ( + prev && + node.type === prev.type && + node.type in MERGEABLE_NODES && + mergeable(prev) && + mergeable(node) + ) { + node = MERGEABLE_NODES[node.type].call( + self, prev, node + ); + } + + if (node !== prev) { + children.push(node); + } + + if (self.atStart && tokens.length) { + self.exitStart(); + } + + return node; + }; + + /** + * Remove `subvalue` from `value`. + * `subvalue` must be at the start of `value`. + * + * @example + * eat('foo')({type: 'text', value: 'foo'}); + * + * @param {string} subvalue - Removed from `value`, + * and passed to `updatePosition`. + * @return {Function} - Wrapper around `add`, which + * also adds `position` to node. + */ + function eat(subvalue) { + var indent = getOffset(); + var pos = position(); + var current = now(); + + validateEat(subvalue); + + /** + * Add the given arguments, add `position` to + * the returned node, and return the node. + * + * @param {Object} node - Node to add. + * @param {Object} [parent] - Node to insert into. + * @return {Node} - Added node. + */ + function apply(node, parent) { + return pos(add(pos(node), parent), indent); + } + + /** + * Functions just like apply, but resets the + * content: the line and column are reversed, + * and the eaten value is re-added. + * + * This is useful for nodes with a single + * type of content, such as lists and tables. + * + * See `apply` above for what parameters are + * expected. + * + * @return {Node} - Added node. + */ + function reset() { + var node = apply.apply(null, arguments); + + line = current.line; + column = current.column; + value = subvalue + value; + + return node; + } + + /** + * Test the position, after eating, and reverse + * to a not-eaten state. + * + * @return {Position} - Position after eating `subvalue`. + */ + function test() { + var result = pos({}); + + line = current.line; + column = current.column; + value = subvalue + value; + + return result.position; + } + + apply.reset = reset; + apply.test = reset.test = test; + + value = value.substring(subvalue.length); + + updatePosition(subvalue); + + indent = indent(); + + return apply; + } + + /* + * Expose `now` on `eat`. + */ + + eat.now = now; + + /* + * Expose `file` on `eat`. + */ + + eat.file = self.file; + + /* + * Sync initial offset. + */ + + updatePosition(EMPTY); + + /* + * Iterate over `value`, and iterate over all + * tokenizers. When one eats something, re-iterate + * with the remaining value. If no tokenizer eats, + * something failed (should not happen) and an + * exception is thrown. + */ + + while (value) { + index = -1; + length = methods.length; + matched = false; + + while (++index < length) { + name = methods[index]; + method = tokenizers[name]; + + if ( + method && + (!method.onlyAtStart || self.atStart) && + (!method.notInList || !self.inList) && + (!method.notInBlock || !self.inBlock) && + (!method.notInLink || !self.inLink) + ) { + valueLength = value.length; + + method.apply(self, [eat, value]); + + matched = valueLength !== value.length; + + if (matched) { + break; + } + } + } + + /* istanbul ignore if */ + if (!matched) { + self.file.fail(ERR_INFINITE_LOOP, eat.now()); + + /* + * Errors are not thrown on `File#fail` + * when `quiet: true`. + */ + + break; + } + } + + self.eof = now(); + + return tokens; + } + + return tokenize; +} + +/* + * Expose tokenizers for block-level nodes. + */ + +Parser.prototype.blockTokenizers = { + 'yamlFrontMatter': tokenizeYAMLFrontMatter, + 'newline': tokenizeNewline, + 'indentedCode': tokenizeIndentedCode, + 'fencedCode': tokenizeFencedCode, + 'blockquote': tokenizeBlockquote, + 'atxHeading': tokenizeATXHeading, + 'thematicBreak': tokenizeThematicBreak, + 'list': tokenizeList, + 'setextHeading': tokenizeSetextHeading, + 'html': tokenizeBlockHTML, + 'footnote': tokenizeFootnoteDefinition, + 'definition': tokenizeDefinition, + 'table': tokenizeTable, + 'paragraph': tokenizeParagraph +}; + +/* + * Expose order in which to parse block-level nodes. + */ + +Parser.prototype.blockMethods = [ + 'yamlFrontMatter', + 'newline', + 'indentedCode', + 'fencedCode', + 'blockquote', + 'atxHeading', + 'thematicBreak', + 'list', + 'setextHeading', + 'html', + 'footnote', + 'definition', + 'table', + 'paragraph' +]; + +/** + * Block tokenizer. + * + * @example + * var parser = new Parser(); + * parser.tokenizeBlock('> foo.'); + * + * @param {string} value - Content. + * @return {Array.<Object>} - Nodes. + */ + +Parser.prototype.tokenizeBlock = tokenizeFactory(BLOCK); + +/* + * Expose tokenizers for inline-level nodes. + */ + +Parser.prototype.inlineTokenizers = { + 'escape': tokenizeEscape, + 'autoLink': tokenizeAutoLink, + 'url': tokenizeURL, + 'html': tokenizeInlineHTML, + 'link': tokenizeLink, + 'reference': tokenizeReference, + 'strong': tokenizeStrong, + 'emphasis': tokenizeEmphasis, + 'deletion': tokenizeDeletion, + 'code': tokenizeInlineCode, + 'break': tokenizeBreak, + 'text': tokenizeText +}; + +/* + * Expose order in which to parse inline-level nodes. + */ + +Parser.prototype.inlineMethods = [ + 'escape', + 'autoLink', + 'url', + 'html', + 'link', + 'reference', + 'strong', + 'emphasis', + 'deletion', + 'code', + 'break', + 'text' +]; + +/** + * Inline tokenizer. + * + * @example + * var parser = new Parser(); + * parser.tokenizeInline('_foo_'); + * + * @param {string} value - Content. + * @return {Array.<Object>} - Nodes. + */ + +Parser.prototype.tokenizeInline = tokenizeFactory(INLINE); + +/* + * Expose `tokenizeFactory` so dependencies could create + * their own tokenizers. + */ + +Parser.prototype.tokenizeFactory = tokenizeFactory; + +/* + * Expose `attacher`. + */ + +module.exports = Parser; |