Alex Kocharin
10 years ago
3 changed files with 220 additions and 1 deletions
@ -0,0 +1,204 @@ |
|||
// Process *this* and _that_
|
|||
|
|||
'use strict'; |
|||
|
|||
|
|||
function isAlphaNum(code) { |
|||
return (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) || |
|||
(code >= 0x41 /* A */ && code <= 0x5A /* Z */) || |
|||
(code >= 0x61 /* a */ && code <= 0x7A /* z */); |
|||
} |
|||
|
|||
// returns the amount of markers (1, 2, 3), or -1 on failure;
|
|||
// "start" should point at a valid marker
|
|||
function parseStart(state, start) { |
|||
var pos = start, lastChar, count, |
|||
max = Math.min(state.posMax, pos + 4), |
|||
marker = state.src.charCodeAt(start); |
|||
|
|||
lastChar = state.pending.length !== 0 ? state.pending.charCodeAt(state.pending.length - 1) : -1; |
|||
|
|||
if (lastChar === marker) { return -1; } |
|||
|
|||
while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; } |
|||
if (pos >= max) { return -1; } |
|||
count = pos - start; |
|||
|
|||
// Quoting spec:
|
|||
//
|
|||
// Character can open emphasis iff
|
|||
// 1. it is not part of a sequence of four or more unescaped markers,
|
|||
// 2. it is not followed by whitespace,
|
|||
// 3. it is "_" and it is not preceded by an ASCII alphanumeric character, and
|
|||
// 4. either it is not followed by a marker or it is followed immediately by strong emphasis.
|
|||
|
|||
if (count >= 4) { |
|||
// check condition 1
|
|||
// sequence of four or more unescaped markers can't start an emphasis
|
|||
return -1; |
|||
} |
|||
|
|||
// check condition 2, marker followed by whitespace
|
|||
if (state.src.charCodeAt(pos) === 0x20) { return -1; } |
|||
|
|||
if (marker === 0x5F /* _ */) { |
|||
// check condition 3, if it's the beginning of the word
|
|||
// we need to look back for this
|
|||
if (isAlphaNum(lastChar)) { return -1; } |
|||
} |
|||
|
|||
return count; |
|||
} |
|||
|
|||
// returns the amount of markers (1, 2, 3), or -1 on failure;
|
|||
// "start" should point at a valid marker
|
|||
function parseEnd(state, start) { |
|||
var pos = start, lastChar, count, |
|||
max = Math.min(state.posMax, pos + 4), |
|||
marker = state.src.charCodeAt(start); |
|||
|
|||
lastChar = state.pending.length !== 0 ? state.pending.charCodeAt(state.pending.length - 1) : -1; |
|||
|
|||
if (lastChar === marker) { return -1; } |
|||
|
|||
while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; } |
|||
count = pos - start; |
|||
|
|||
// Quoting spec:
|
|||
//
|
|||
// Character can close emphasis iff
|
|||
// 1. it is not part of a sequence of four or more unescaped markers,
|
|||
// 2. it is not preceded by whitespace,
|
|||
// 3. it is not "_" or it is not followed by an ASCII alphanumeric character
|
|||
|
|||
if (count >= 4) { |
|||
// check condition 1
|
|||
// sequence of four or more unescaped markers can't start an emphasis
|
|||
return -1; |
|||
} |
|||
|
|||
// check condition 2, marker preceded by whitespace
|
|||
if (lastChar === 0x20) { return -1; } |
|||
|
|||
if (marker === 0x5F) { |
|||
// check condition 3, if it's the end of the word
|
|||
if (pos < max && isAlphaNum(state.src.charCodeAt(pos))) { return -1; } |
|||
} |
|||
|
|||
return count; |
|||
} |
|||
|
|||
module.exports = function emphasis(state/*, silent*/) { |
|||
var startCount, |
|||
count, |
|||
oldLength, |
|||
oldPending, |
|||
found, |
|||
ok, |
|||
i, |
|||
oldCount, |
|||
newCount, |
|||
len, |
|||
rules, |
|||
stack, |
|||
breakOutOfOuterLoop, |
|||
max = state.posMax, |
|||
start = state.pos, |
|||
marker = state.src.charCodeAt(start); |
|||
|
|||
if (marker !== 0x5F/* _ */ && marker !== 0x2A /* * */) { return false; } |
|||
|
|||
startCount = parseStart(state, start); |
|||
if (startCount < 0) { return false; } |
|||
|
|||
oldLength = state.tokens.length; |
|||
oldPending = state.pending; |
|||
|
|||
state.pos = start + startCount; |
|||
stack = [ startCount ]; |
|||
rules = state.lexer.rules; |
|||
len = rules.length; |
|||
|
|||
while (state.pos < max) { |
|||
if (state.src.charCodeAt(state.pos) === marker) { |
|||
count = parseEnd(state, state.pos); |
|||
if (count >= 1) { |
|||
oldCount = stack.pop(); |
|||
newCount = count; |
|||
|
|||
while (oldCount !== newCount) { |
|||
if (oldCount === 3) { |
|||
// e.g. `***foo*`
|
|||
stack.push(3 - newCount); |
|||
break; |
|||
} |
|||
|
|||
if (newCount < oldCount) { |
|||
// assert(oldCount == 2 && newCount == 1)
|
|||
// i.e. `**foo* bar*`
|
|||
// not valid for now, but might be in the future
|
|||
|
|||
// eslint is misconfigured, so it doesn't accept "break MAIN;"
|
|||
// here is a crappy workaround
|
|||
breakOutOfOuterLoop = true; |
|||
break; |
|||
} |
|||
|
|||
// assert(newCount > oldCount)
|
|||
newCount -= oldCount; |
|||
|
|||
if (stack.length === 0) { break; } |
|||
state.pos += oldCount; |
|||
oldCount = stack.pop(); |
|||
} |
|||
|
|||
if (breakOutOfOuterLoop) { break; } |
|||
|
|||
if (stack.length === 0) { |
|||
startCount = oldCount; |
|||
found = true; |
|||
break; |
|||
} |
|||
state.pos += count; |
|||
continue; |
|||
} |
|||
|
|||
count = parseStart(state, state.pos); |
|||
if (count >= 1) { |
|||
stack.push(count); |
|||
state.pos += count; |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
for (i = 0; i < len; i++) { |
|||
if (rules[i] !== emphasis) { ok = rules[i](state); } |
|||
if (ok) { break; } |
|||
} |
|||
|
|||
if (!ok) { state.pending += state.src[state.pos++]; } |
|||
} |
|||
|
|||
// restore old state
|
|||
state.tokens.length = oldLength; |
|||
state.pending = oldPending; |
|||
|
|||
if (!found) { |
|||
// parser failed to find ending tag, so it's not valid emphasis
|
|||
state.pos = start; |
|||
return false; |
|||
} |
|||
|
|||
// found!
|
|||
state.posMax = state.pos; |
|||
state.pos = start + startCount; |
|||
if (state.pending) { state.pushPending(); } |
|||
if (startCount === 2 || startCount === 3) { state.push({ type: 'strong_open' }); } |
|||
if (startCount === 1 || startCount === 3) { state.push({ type: 'em_open' }); } |
|||
state.lexer.tokenize(state); |
|||
if (startCount === 1 || startCount === 3) { state.push({ type: 'em_close' }); } |
|||
if (startCount === 2 || startCount === 3) { state.push({ type: 'strong_close' }); } |
|||
state.pos = state.posMax + startCount; |
|||
state.posMax = max; |
|||
return true; |
|||
}; |
Loading…
Reference in new issue