state_block.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. // Parser state class
  2. 'use strict';
  3. var Token = require('../token');
  4. var isSpace = require('../common/utils').isSpace;
  5. function StateBlock(src, md, env, tokens) {
  6. var ch, s, start, pos, len, indent, offset, indent_found;
  7. this.src = src;
  8. // link to parser instance
  9. this.md = md;
  10. this.env = env;
  11. //
  12. // Internal state vartiables
  13. //
  14. this.tokens = tokens;
  15. this.bMarks = []; // line begin offsets for fast jumps
  16. this.eMarks = []; // line end offsets for fast jumps
  17. this.tShift = []; // offsets of the first non-space characters (tabs not expanded)
  18. this.sCount = []; // indents for each line (tabs expanded)
  19. // An amount of virtual spaces (tabs expanded) between beginning
  20. // of each line (bMarks) and real beginning of that line.
  21. //
  22. // It exists only as a hack because blockquotes override bMarks
  23. // losing information in the process.
  24. //
  25. // It's used only when expanding tabs, you can think about it as
  26. // an initial tab length, e.g. bsCount=21 applied to string `\t123`
  27. // means first tab should be expanded to 4-21%4 === 3 spaces.
  28. //
  29. this.bsCount = [];
  30. // block parser variables
  31. this.blkIndent = 0; // required block content indent (for example, if we are
  32. // inside a list, it would be positioned after list marker)
  33. this.line = 0; // line index in src
  34. this.lineMax = 0; // lines count
  35. this.tight = false; // loose/tight mode for lists
  36. this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any)
  37. this.listIndent = -1; // indent of the current list block (-1 if there isn't any)
  38. // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference'
  39. // used in lists to determine if they interrupt a paragraph
  40. this.parentType = 'root';
  41. this.level = 0;
  42. // renderer
  43. this.result = '';
  44. // Create caches
  45. // Generate markers.
  46. s = this.src;
  47. indent_found = false;
  48. for (start = pos = indent = offset = 0, len = s.length; pos < len; pos++) {
  49. ch = s.charCodeAt(pos);
  50. if (!indent_found) {
  51. if (isSpace(ch)) {
  52. indent++;
  53. if (ch === 0x09) {
  54. offset += 4 - offset % 4;
  55. } else {
  56. offset++;
  57. }
  58. continue;
  59. } else {
  60. indent_found = true;
  61. }
  62. }
  63. if (ch === 0x0A || pos === len - 1) {
  64. if (ch !== 0x0A) { pos++; }
  65. this.bMarks.push(start);
  66. this.eMarks.push(pos);
  67. this.tShift.push(indent);
  68. this.sCount.push(offset);
  69. this.bsCount.push(0);
  70. indent_found = false;
  71. indent = 0;
  72. offset = 0;
  73. start = pos + 1;
  74. }
  75. }
  76. // Push fake entry to simplify cache bounds checks
  77. this.bMarks.push(s.length);
  78. this.eMarks.push(s.length);
  79. this.tShift.push(0);
  80. this.sCount.push(0);
  81. this.bsCount.push(0);
  82. this.lineMax = this.bMarks.length - 1; // don't count last fake line
  83. }
  84. // Push new token to "stream".
  85. //
  86. StateBlock.prototype.push = function (type, tag, nesting) {
  87. var token = new Token(type, tag, nesting);
  88. token.block = true;
  89. if (nesting < 0) this.level--; // closing tag
  90. token.level = this.level;
  91. if (nesting > 0) this.level++; // opening tag
  92. this.tokens.push(token);
  93. return token;
  94. };
  95. StateBlock.prototype.isEmpty = function isEmpty(line) {
  96. return this.bMarks[line] + this.tShift[line] >= this.eMarks[line];
  97. };
  98. StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) {
  99. for (var max = this.lineMax; from < max; from++) {
  100. if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) {
  101. break;
  102. }
  103. }
  104. return from;
  105. };
  106. // Skip spaces from given position.
  107. StateBlock.prototype.skipSpaces = function skipSpaces(pos) {
  108. var ch;
  109. for (var max = this.src.length; pos < max; pos++) {
  110. ch = this.src.charCodeAt(pos);
  111. if (!isSpace(ch)) { break; }
  112. }
  113. return pos;
  114. };
  115. // Skip spaces from given position in reverse.
  116. StateBlock.prototype.skipSpacesBack = function skipSpacesBack(pos, min) {
  117. if (pos <= min) { return pos; }
  118. while (pos > min) {
  119. if (!isSpace(this.src.charCodeAt(--pos))) { return pos + 1; }
  120. }
  121. return pos;
  122. };
  123. // Skip char codes from given position
  124. StateBlock.prototype.skipChars = function skipChars(pos, code) {
  125. for (var max = this.src.length; pos < max; pos++) {
  126. if (this.src.charCodeAt(pos) !== code) { break; }
  127. }
  128. return pos;
  129. };
  130. // Skip char codes reverse from given position - 1
  131. StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) {
  132. if (pos <= min) { return pos; }
  133. while (pos > min) {
  134. if (code !== this.src.charCodeAt(--pos)) { return pos + 1; }
  135. }
  136. return pos;
  137. };
  138. // cut lines range from source.
  139. StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) {
  140. var i, lineIndent, ch, first, last, queue, lineStart,
  141. line = begin;
  142. if (begin >= end) {
  143. return '';
  144. }
  145. queue = new Array(end - begin);
  146. for (i = 0; line < end; line++, i++) {
  147. lineIndent = 0;
  148. lineStart = first = this.bMarks[line];
  149. if (line + 1 < end || keepLastLF) {
  150. // No need for bounds check because we have fake entry on tail.
  151. last = this.eMarks[line] + 1;
  152. } else {
  153. last = this.eMarks[line];
  154. }
  155. while (first < last && lineIndent < indent) {
  156. ch = this.src.charCodeAt(first);
  157. if (isSpace(ch)) {
  158. if (ch === 0x09) {
  159. lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4;
  160. } else {
  161. lineIndent++;
  162. }
  163. } else if (first - lineStart < this.tShift[line]) {
  164. // patched tShift masked characters to look like spaces (blockquotes, list markers)
  165. lineIndent++;
  166. } else {
  167. break;
  168. }
  169. first++;
  170. }
  171. if (lineIndent > indent) {
  172. // partially expanding tabs in code blocks, e.g '\t\tfoobar'
  173. // with indent=2 becomes ' \tfoobar'
  174. queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last);
  175. } else {
  176. queue[i] = this.src.slice(first, last);
  177. }
  178. }
  179. return queue.join('');
  180. };
  181. // re-export Token class to use in block rules
  182. StateBlock.prototype.Token = Token;
  183. module.exports = StateBlock;