// Parser state class import Token from '../token.mjs' import { isSpace } from '../common/utils.mjs' function StateBlock (src, md, env, tokens) { this.src = src // link to parser instance this.md = md this.env = env // // Internal state vartiables // this.tokens = tokens this.bMarks = [] // line begin offsets for fast jumps this.eMarks = [] // line end offsets for fast jumps this.tShift = [] // offsets of the first non-space characters (tabs not expanded) this.sCount = [] // indents for each line (tabs expanded) // An amount of virtual spaces (tabs expanded) between beginning // of each line (bMarks) and real beginning of that line. // // It exists only as a hack because blockquotes override bMarks // losing information in the process. // // It's used only when expanding tabs, you can think about it as // an initial tab length, e.g. bsCount=21 applied to string `\t123` // means first tab should be expanded to 4-21%4 === 3 spaces. // this.bsCount = [] // block parser variables // required block content indent (for example, if we are // inside a list, it would be positioned after list marker) this.blkIndent = 0 this.line = 0 // line index in src this.lineMax = 0 // lines count this.tight = false // loose/tight mode for lists this.ddIndent = -1 // indent of the current dd block (-1 if there isn't any) this.listIndent = -1 // indent of the current list block (-1 if there isn't any) // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' // used in lists to determine if they interrupt a paragraph this.parentType = 'root' this.level = 0 // Create caches // Generate markers. const s = this.src for (let start = 0, pos = 0, indent = 0, offset = 0, len = s.length, indent_found = false; pos < len; pos++) { const ch = s.charCodeAt(pos) if (!indent_found) { if (isSpace(ch)) { indent++ if (ch === 0x09) { offset += 4 - offset % 4 } else { offset++ } continue } else { indent_found = true } } if (ch === 0x0A || pos === len - 1) { if (ch !== 0x0A) { pos++ } this.bMarks.push(start) this.eMarks.push(pos) this.tShift.push(indent) this.sCount.push(offset) this.bsCount.push(0) indent_found = false indent = 0 offset = 0 start = pos + 1 } } // Push fake entry to simplify cache bounds checks this.bMarks.push(s.length) this.eMarks.push(s.length) this.tShift.push(0) this.sCount.push(0) this.bsCount.push(0) this.lineMax = this.bMarks.length - 1 // don't count last fake line } // Push new token to "stream". // StateBlock.prototype.push = function (type, tag, nesting) { const token = new Token(type, tag, nesting) token.block = true if (nesting < 0) this.level-- // closing tag token.level = this.level if (nesting > 0) this.level++ // opening tag this.tokens.push(token) return token } StateBlock.prototype.isEmpty = function isEmpty (line) { return this.bMarks[line] + this.tShift[line] >= this.eMarks[line] } StateBlock.prototype.skipEmptyLines = function skipEmptyLines (from) { for (let max = this.lineMax; from < max; from++) { if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { break } } return from } // Skip spaces from given position. StateBlock.prototype.skipSpaces = function skipSpaces (pos) { for (let max = this.src.length; pos < max; pos++) { const ch = this.src.charCodeAt(pos) if (!isSpace(ch)) { break } } return pos } // Skip spaces from given position in reverse. StateBlock.prototype.skipSpacesBack = function skipSpacesBack (pos, min) { if (pos <= min) { return pos } while (pos > min) { if (!isSpace(this.src.charCodeAt(--pos))) { return pos + 1 } } return pos } // Skip char codes from given position StateBlock.prototype.skipChars = function skipChars (pos, code) { for (let max = this.src.length; pos < max; pos++) { if (this.src.charCodeAt(pos) !== code) { break } } return pos } // Skip char codes reverse from given position - 1 StateBlock.prototype.skipCharsBack = function skipCharsBack (pos, code, min) { if (pos <= min) { return pos } while (pos > min) { if (code !== this.src.charCodeAt(--pos)) { return pos + 1 } } return pos } // cut lines range from source. StateBlock.prototype.getLines = function getLines (begin, end, indent, keepLastLF) { if (begin >= end) { return '' } const queue = new Array(end - begin) for (let i = 0, line = begin; line < end; line++, i++) { let lineIndent = 0 const lineStart = this.bMarks[line] let first = lineStart let last if (line + 1 < end || keepLastLF) { // No need for bounds check because we have fake entry on tail. last = this.eMarks[line] + 1 } else { last = this.eMarks[line] } while (first < last && lineIndent < indent) { const ch = this.src.charCodeAt(first) if (isSpace(ch)) { if (ch === 0x09) { lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4 } else { lineIndent++ } } else if (first - lineStart < this.tShift[line]) { // patched tShift masked characters to look like spaces (blockquotes, list markers) lineIndent++ } else { break } first++ } if (lineIndent > indent) { // partially expanding tabs in code blocks, e.g '\t\tfoobar' // with indent=2 becomes ' \tfoobar' queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last) } else { queue[i] = this.src.slice(first, last) } } return queue.join('') } // re-export Token class to use in block rules StateBlock.prototype.Token = Token export default StateBlock