123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- // 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
|