From 9728b685ec547c6cca78ecfe61cd5763d9635e40 Mon Sep 17 00:00:00 2001 From: Vitaly Puzrin Date: Fri, 12 Sep 2014 15:26:33 +0400 Subject: [PATCH] Rewritten string fetch for inline tokenizer + list fixes --- lib/helpers.js | 34 +- lib/lexer_block/blockquote.js | 2 +- lib/lexer_block/code.js | 3 +- lib/lexer_block/fences.js | 6 +- lib/lexer_block/heading.js | 2 +- lib/lexer_block/lheading.js | 2 +- lib/lexer_block/list.js | 52 ++- lib/lexer_block/paragraph.js | 4 +- lib/lexer_block/table.js | 4 +- lib/lexer_inline.js | 11 +- lib/renderer.js | 27 +- lib/state.js | 7 +- test/fixtures/stmd/bad.txt | 582 +++------------------------------- test/fixtures/stmd/good.txt | 324 +++++++++++++++++++ 14 files changed, 475 insertions(+), 585 deletions(-) diff --git a/lib/helpers.js b/lib/helpers.js index e771f9e..eae4bf3 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -49,16 +49,36 @@ function skipCharsBack(state, pos, code, min) { } // cut lines range from source. -function getLines(state, begin, end, keepLastLF) { - var last; +function getLines(state, begin, end, indent, keepLastLF) { + var i, first, last, queue, + line = begin; - if (keepLastLF) { - last = end < state.lineMax ? state.bMarks[end] : state.src.length; - } else { - last = end < state.lineMax ? state.eMarks[end - 1] : state.src.length; + if (begin >= end) { + return ''; } - return state.src.slice(state.bMarks[begin], last); + // Opt: don't use push queue for single line; + if (line + 1 === end) { + first = state.bMarks[line] + Math.min(state.tShift[line], indent); + last = keepLastLF ? state.bMarks[end] : state.eMarks[end - 1]; + return state.src.slice(first, last); + } + + queue = new Array(end - begin); + + for (i = 0; line < end; line++, i++) { + first = state.bMarks[line] + Math.min(state.tShift[line], indent); + + if (line + 1 < end || keepLastLF) { + last = state.bMarks[line + 1]; + } else { + last = state.eMarks[line]; + } + + queue[i] = state.src.slice(first, last); + } + + return queue.join(''); } exports.isWhiteSpace = isWhiteSpace; diff --git a/lib/lexer_block/blockquote.js b/lib/lexer_block/blockquote.js index b18e8c5..d606d49 100644 --- a/lib/lexer_block/blockquote.js +++ b/lib/lexer_block/blockquote.js @@ -47,7 +47,7 @@ module.exports = function blockquote(state, startLine, endLine, silent) { if (isEmpty(state, nextLine)) { break; } nextLine++; } - subState = state.clone(getLines(state, startLine, nextLine, true) + subState = state.clone(getLines(state, startLine, nextLine, 0, true) .replace(/^ {0,3}> ?/mg, '')); state.lexerBlock.tokenize(subState, 0, insideLines); nextLine = startLine = subState.line + startLine; diff --git a/lib/lexer_block/code.js b/lib/lexer_block/code.js index e569280..c4b6bf1 100644 --- a/lib/lexer_block/code.js +++ b/lib/lexer_block/code.js @@ -34,8 +34,7 @@ module.exports = function code(state, startLine, endLine, silent) { state.tokens.push({ type: 'code', - content: getLines(state, startLine, last, true).replace( - new RegExp('^ {1,' + (4 + state.blkIndent) + '}', 'gm'), '') + content: getLines(state, startLine, last, 4 + state.blkIndent, true) }); state.line = nextLine; diff --git a/lib/lexer_block/fences.js b/lib/lexer_block/fences.js index 57f519e..42fab96 100644 --- a/lib/lexer_block/fences.js +++ b/lib/lexer_block/fences.js @@ -85,11 +85,7 @@ module.exports = function fences(state, startLine, endLine, silent) { state.tokens.push({ type: 'fence', params: params ? params.split(/\s+/g) : [], - content: len === 0 ? - getLines(state, startLine + 1, nextLine, true) - : - getLines(state, startLine + 1, nextLine, true) - .replace(RegExp('^ {1,' + len + '}', 'mg'), '') + content: getLines(state, startLine + 1, nextLine, len, true) }); state.line = nextLine + (haveEndMarker ? 1 : 0); diff --git a/lib/lexer_block/heading.js b/lib/lexer_block/heading.js index 6023e47..f61f02a 100644 --- a/lib/lexer_block/heading.js +++ b/lib/lexer_block/heading.js @@ -58,7 +58,7 @@ module.exports = function heading(state, startLine, endLine, silent) { state.tokens.push({ type: 'heading_open', level: level }); // only if header is not empty if (pos < max) { - state.lexerInline.tokenize(state, pos, max); + state.lexerInline.tokenize(state, state.src.slice(pos, max)); } state.tokens.push({ type: 'heading_close', level: level }); diff --git a/lib/lexer_block/lheading.js b/lib/lexer_block/lheading.js index 1b1bb03..0839b0b 100644 --- a/lib/lexer_block/lheading.js +++ b/lib/lexer_block/lheading.js @@ -37,7 +37,7 @@ module.exports = function lheading(state, startLine, endLine, silent) { max = skipCharsBack(state, state.eMarks[startLine], 0x20/* space */, pos); state.tokens.push({ type: 'heading_open', level: marker === 0x3D/* = */ ? 1 : 2 }); - state.lexerInline.tokenize(state, pos, max); + state.lexerInline.tokenize(state, state.src.slice(pos, max)); state.tokens.push({ type: 'heading_close', level: marker === 0x3D/* = */ ? 1 : 2 }); state.line = next + 1; diff --git a/lib/lexer_block/list.js b/lib/lexer_block/list.js index 4e0a084..78e7279 100644 --- a/lib/lexer_block/list.js +++ b/lib/lexer_block/list.js @@ -85,11 +85,12 @@ module.exports = function list(state, startLine, endLine, silent) { max, indentAfterMarker, markerValue, + markerCharCode, isOrdered, contentStart, listTokIdx, - endOfList; - //rules_named = state.lexerBlock.rules_named; + prevEmptyEnd, + rules_named = state.lexerBlock.rules_named; // Detect list type and position after marker if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { @@ -99,6 +100,8 @@ module.exports = function list(state, startLine, endLine, silent) { } else { return false; } + // We should terminate list on style change. Remember first one to compare. + markerCharCode = state.src.charCodeAt(posAfterMarker - 1); // For validation mode we can terminate immediately if (silent) { return true; } @@ -128,18 +131,9 @@ module.exports = function list(state, startLine, endLine, silent) { // nextLine = startLine; - endOfList = false; - - while (nextLine < endLine && !endOfList) { - if (state.tShift[startLine] < state.blkIndent) { return -1; } - if (isOrdered) { - posAfterMarker = skipOrderedListMarker(state, nextLine); - if (posAfterMarker < 0) { break; } - } else { - posAfterMarker = skipBulletListMarker(state, nextLine); - if (posAfterMarker < 0) { break; } - } + prevEmptyEnd = false; + while (nextLine < endLine) { contentStart = skipSpaces(state, posAfterMarker); max = state.eMarks[nextLine]; @@ -165,18 +159,22 @@ module.exports = function list(state, startLine, endLine, silent) { // Run sublexer & write tokens state.tokens.push({ type: 'list_item_open' }); - nextLine++; + //nextLine++; oldIndent = state.blkIndent; oldTight = state.tight; state.blkIndent = state.tShift[startLine] = indent; + state.tight = true; state.lexerBlock.tokenize(state, startLine, endLine, true); - // If any of list item is loose, mark list as loose - if (!state.tight || isEmpty(state, state.line - 1)) { + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { state.tokens[listTokIdx].tight = false; } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = (state.line - startLine) > 1 && isEmpty(state, state.line - 1); state.blkIndent = state.tShift[startLine] = oldIndent; state.tight = oldTight; @@ -195,6 +193,28 @@ module.exports = function list(state, startLine, endLine, silent) { break; } } + + // + // Try to ckeck if list is terminated or continued. + // + + // fail if terminating block found + if (rules_named.fences(state, nextLine, endLine, true)) { break; } + if (rules_named.blockquote(state, nextLine, endLine, true)) { break; } + if (rules_named.hr(state, nextLine, endLine, true)) { break; } + + // fail if list has another type + if (isOrdered) { + posAfterMarker = skipOrderedListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } else { + posAfterMarker = skipBulletListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } + + if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; } + + } // Finilize list diff --git a/lib/lexer_block/paragraph.js b/lib/lexer_block/paragraph.js index 2ccfff2..a3ac70b 100644 --- a/lib/lexer_block/paragraph.js +++ b/lib/lexer_block/paragraph.js @@ -4,6 +4,7 @@ var isEmpty = require('../helpers').isEmpty; +var getLines = require('../helpers').getLines; module.exports = function paragraph(state, startLine/*, endLine*/) { @@ -32,8 +33,7 @@ module.exports = function paragraph(state, startLine/*, endLine*/) { state.tokens.push({ type: 'paragraph_open' }); state.lexerInline.tokenize( state, - state.bMarks[startLine] + state.tShift[startLine], - state.eMarks[nextLine - 1] + getLines(state, startLine, nextLine, state.blkIndent, false) ); state.tokens.push({ type: 'paragraph_close' }); diff --git a/lib/lexer_block/table.js b/lib/lexer_block/table.js index de6a158..eeb1576 100644 --- a/lib/lexer_block/table.js +++ b/lib/lexer_block/table.js @@ -53,7 +53,7 @@ module.exports = function table(state, startLine, endLine, silent) { for (i = 0; i < rows.length; i++) { state.tokens.push({ type: 'th_open', align: aligns[i] }); subState = state.clone(rows[i].trim()); - state.lexerInline.tokenize(subState, 0, subState.eMarks[0]); + state.lexerInline.tokenize(subState, subState.src); state.tokens.push({ type: 'th_close' }); } state.tokens.push({ type: 'tr_close' }); @@ -67,7 +67,7 @@ module.exports = function table(state, startLine, endLine, silent) { for (i = 0; i < rows.length; i++) { state.tokens.push({ type: 'td_open', align: aligns[i] }); subState = state.clone(rows[i].replace(/^\|? *| *\|?$/g, '')); - state.lexerInline.tokenize(subState, 0, subState.eMarks[0]); + state.lexerInline.tokenize(subState, subState.src); state.tokens.push({ type: 'td_close' }); } state.tokens.push({ type: 'tr_close' }); diff --git a/lib/lexer_inline.js b/lib/lexer_inline.js index f65e821..06bf2fe 100644 --- a/lib/lexer_inline.js +++ b/lib/lexer_inline.js @@ -10,10 +10,10 @@ var rules = []; // Pure text -rules.push(function text(state, begin, end) { +rules.push(function text(state, str, begin, end) { state.tokens.push({ type: 'text', - content: state.src.slice(begin, end).replace(/^ {1,}/mg, '') + content: str.slice(begin, end).replace(/^ {1,}/mg, '') }); state.pos = end; @@ -107,11 +107,12 @@ LexerInline.prototype.after = function (name, fn) { // Generate tokens for input range // -LexerInline.prototype.tokenize = function (state, begin, end) { +LexerInline.prototype.tokenize = function (state, str) { var ok, i, rules = this.rules, len = this.rules.length, - pos = begin; + pos = 0, + end = str.length; while (pos < end) { @@ -123,7 +124,7 @@ LexerInline.prototype.tokenize = function (state, begin, end) { // - return true for (i = 0; i < len; i++) { - ok = rules[i](state, pos, end); + ok = rules[i](state, str, pos, end); if (ok) { break; } } diff --git a/lib/renderer.js b/lib/renderer.js index 7cdb15b..25c88eb 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -40,12 +40,12 @@ rules.blockquote_close = function (state, token, idx) { }; -rules.code = function (state, token) { - state.result += '
' + escapeHtml(token.content) + '
\n'; +rules.code = function (state, token, idx) { + state.result += '
' + escapeHtml(token.content) + '
' + getBreak(state, idx); }; -rules.fence = function (state, token) { +rules.fence = function (state, token, idx) { var langMark = ''; var langPrefix = state.options.codeLangPrefix || ''; @@ -53,7 +53,9 @@ rules.fence = function (state, token) { langMark = ' class="' + langPrefix + escapeHtml(token.params[0]) + '"'; } - state.result += '
' + escapeHtml(token.content) + '
\n'; + state.result += '
'
+                  + escapeHtml(token.content)
+                  + '
' + getBreak(state, idx); }; @@ -130,7 +132,7 @@ rules.td_close = function (state /*, token, idx*/) { }; -rules.text = function (state, token) { +rules.text = function (state, token /*, idx*/) { state.result += escapeHtml(unescapeMd(token.content)); }; @@ -142,7 +144,7 @@ function Renderer() { } Renderer.prototype.render = function (state) { - var i, len, rule, name, + var i, len, rule, name, next, tokens = state.tokens, rules = this.rules, tightStack = []; @@ -170,7 +172,18 @@ Renderer.prototype.render = function (state) { // in tight mode just ignore paragraphs for lists // TODO - track right nesting to blockquotes - if ((name === 'paragraph_open' || name === 'paragraph_close') && state.tight) { + if (name === 'paragraph_open' && state.tight) { + continue; + } + if (name === 'paragraph_close' && state.tight) { + // Quick hack - texts should have LF if followed by blocks + if (i + 1 < state.tokens.length) { + next = state.tokens[i + 1].type; + if (next === 'bullet_list_open' || next === 'ordered_list_open') { + state.result += '\n'; + } + } + continue; } rule(state, tokens[i], i); diff --git a/lib/state.js b/lib/state.js index cf60342..339d1d3 100644 --- a/lib/state.js +++ b/lib/state.js @@ -71,6 +71,11 @@ function State(src, lexerBlock, lexerInline, renderer, tokens, options) { this.tShift.push(indent); } + // Push fake entry to simplify cache bounds checks + this.bMarks.push(s.length); + this.eMarks.push(s.length); + this.tShift.push(0); + // inline lexer variables this.pos = 0; // char index in src @@ -78,7 +83,7 @@ function State(src, lexerBlock, lexerInline, renderer, tokens, options) { this.blkLevel = 0; this.blkIndent = 0; this.line = 0; // line index in src - this.lineMax = this.bMarks.length; + this.lineMax = this.bMarks.length - 1; // don't count last fake line this.tight = false; // loose/tight mode for lists // renderer diff --git a/test/fixtures/stmd/bad.txt b/test/fixtures/stmd/bad.txt index 8c0ff7f..a60050a 100644 --- a/test/fixtures/stmd/bad.txt +++ b/test/fixtures/stmd/bad.txt @@ -699,75 +699,6 @@ error: bbb

-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 2147 - -. -> foo - bar -. -
-
foo
-
-
-
bar
-
-. - -error: - -
-
foo
-bar
-
-
- - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 2159 - -. -> ``` -foo -``` -. -
-
-
-

foo

-
-. - -error: - -
-
foo
-
-
-
- - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 2236 - -. -> foo -> -> bar -. -
-

foo

-

bar

-
-. - -error: - -
-

foo

-
- - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 2414 @@ -794,63 +725,13 @@ error:
  1. A paragraph -with two lines.

  2. +with two lines.

    +
    indented code
    +
    +

    > A block quote.

-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 2540 - -. -- foo - - bar - -- foo - - - bar - -- ``` - foo - - - bar - ``` -. - -

bar

- -. - -error: - - - -

bar

- -

bar

-
- - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 2574 @@ -882,55 +763,8 @@ error:
  • foo

    bar
     
    -
  • - - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 2612 - -. -- foo - - bar -. - -. - -error: - - - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 2626 - -. - 10. foo - - bar -. -
      -
    1. foo

      -
      bar
      -
    2. -
    -. - -error: - -
      -
    1. foo

      -
      bar
      -
    2. +

      baz

      +

      > bam

    @@ -956,7 +790,10 @@ src line: 2656 error:
      -
    1. indented code

    2. +
    3. indented code

      +

      paragraph

      +
      more code
      +
    @@ -982,7 +819,10 @@ src line: 2675 error:
      -
    1. indented code

    2. +
    3. indented code

      +

      paragraph

      +
      more code
      +
    @@ -1012,7 +852,10 @@ error:
    1. A paragraph -with two lines.

    2. +with two lines.

      +
      indented code
      +
      +

      > A block quote.

    @@ -1042,7 +885,10 @@ error:
    1. A paragraph -with two lines.

    2. +with two lines.

      +
      indented code
      +
      +

      > A block quote.

    @@ -1072,7 +918,10 @@ error:
    1. A paragraph -with two lines.

    2. +with two lines.

      +
      indented code
      +
      +

      > A block quote.

    @@ -1102,7 +951,10 @@ error:
    1. A paragraph -with two lines.

    2. +with two lines.

      +
      indented code
      +
      +

      > A block quote.

    @@ -1123,17 +975,6 @@ continued here.

    . -error: - -
    -
      -
    1. -Blockquote -continued here.
    2. -
    -
    - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 2880 @@ -1151,156 +992,6 @@ continued here.

    . -error: - -
    -
      -
    1. -Blockquote -continued here.
    2. -
    -
    - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 2904 - -. -- foo - - bar - - baz -. - -. - -error: - - - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 2936 - -. -10) foo - - bar -. -
      -
    1. foo -
        -
      • bar
      • -
    2. -
    -. - -error: - -
      -
    1. foo
        -
      • bar
      • -
    2. -
    - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 3222 - -. -- foo -- bar -+ baz -. - - -. - -error: - - - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 3236 - -. -1. foo -2. bar -3) baz -. -
      -
    1. foo
    2. -
    3. bar
    4. -
    -
      -
    1. baz
    2. -
    -. - -error: - -
      -
    1. foo
    2. -
    3. bar
    4. -
    5. baz
    6. -
    - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 3253 - -. -- foo - -- bar - - -- baz -. - - -. - -error: - - - - - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 3292 @@ -1328,102 +1019,14 @@ src line: 3292 error: -
      bim
    -
    - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 3336 - -. -- foo - - notcode - -- foo - - - code -. - -
    code
    -
    -. - -error: - - - -
    code
    -
    - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 3383 - -. -- a -- b - -- c -. - -. - -error: - - - - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 3398 - -. -* a -* - -* c -. +
  • foo -. - -error: - +
  • bar

    -
  • @@ -1454,39 +1057,6 @@ error: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 3446 - -. -- a -- ``` - b - - - ``` -- c -. - -. - -error: - - -
    - c
    -
    - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 3469 @@ -1510,11 +1080,12 @@ src line: 3469 error: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -src line: 3536 - -. -- a - - b -. - -. - -error: - - - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 3550 @@ -1624,7 +1170,8 @@ src line: 3550 error: . +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +src line: 2936 + +. +10) foo + - bar +. +
      +
    1. foo +
        +
      • bar
      • +
    2. +
    +. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 2950 @@ -1787,6 +1947,79 @@ src line: 3000 . +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +src line: 3222 + +. +- foo +- bar ++ baz +. + + +. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +src line: 3236 + +. +1. foo +2. bar +3) baz +. +
      +
    1. foo
    2. +
    3. bar
    4. +
    +
      +
    1. baz
    2. +
    +. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +src line: 3253 + +. +- foo + +- bar + + +- baz +. + + +. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +src line: 3274 + +. +- foo + + + bar +- baz +. + +

    bar

    + +. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 3318 @@ -1808,6 +2041,28 @@ src line: 3318 . +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +src line: 3336 + +. +- foo + + notcode + +- foo + + + code +. + +
    code
    +
    +. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 3360 @@ -1831,6 +2086,38 @@ src line: 3360 . +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +src line: 3383 + +. +- a +- b + +- c +. + +. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +src line: 3398 + +. +* a +* + +* c +. + +. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 3415 @@ -1849,6 +2136,28 @@ src line: 3415 . +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +src line: 3446 + +. +- a +- ``` + b + + + ``` +- c +. + +. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 3528 @@ -1860,6 +2169,21 @@ src line: 3528 . +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +src line: 3536 + +. +- a + - b +. + +. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src line: 3607