You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
6.3 KiB
207 lines
6.3 KiB
var attention = { |
|
name: 'attention', |
|
tokenize: tokenizeAttention, |
|
resolveAll: resolveAllAttention |
|
} |
|
export default attention |
|
|
|
import assert from 'assert' |
|
import codes from '../character/codes.mjs' |
|
import constants from '../constant/constants.mjs' |
|
import types from '../constant/types.mjs' |
|
import chunkedPush from '../util/chunked-push.mjs' |
|
import chunkedSplice from '../util/chunked-splice.mjs' |
|
import classifyCharacter from '../util/classify-character.mjs' |
|
import movePoint from '../util/move-point.mjs' |
|
import resolveAll from '../util/resolve-all.mjs' |
|
import shallow from '../util/shallow.mjs' |
|
|
|
// Take all events and resolve attention to emphasis or strong. |
|
function resolveAllAttention(events, context) { |
|
var index = -1 |
|
var open |
|
var group |
|
var text |
|
var openingSequence |
|
var closingSequence |
|
var use |
|
var nextEvents |
|
var offset |
|
|
|
// Walk through all events. |
|
// |
|
// Note: performance of this is fine on an mb of normal markdown, but it’s |
|
// a bottleneck for malicious stuff. |
|
while (++index < events.length) { |
|
// Find a token that can close. |
|
if ( |
|
events[index][0] === 'enter' && |
|
events[index][1].type === 'attentionSequence' && |
|
events[index][1]._close |
|
) { |
|
open = index |
|
|
|
// Now walk back to find an opener. |
|
while (open--) { |
|
// Find a token that can open the closer. |
|
if ( |
|
events[open][0] === 'exit' && |
|
events[open][1].type === 'attentionSequence' && |
|
events[open][1]._open && |
|
// If the markers are the same: |
|
context.sliceSerialize(events[open][1]).charCodeAt(0) === |
|
context.sliceSerialize(events[index][1]).charCodeAt(0) |
|
) { |
|
// If the opening can close or the closing can open, |
|
// and the close size *is not* a multiple of three, |
|
// but the sum of the opening and closing size *is* multiple of three, |
|
// then don’t match. |
|
if ( |
|
(events[open][1]._close || events[index][1]._open) && |
|
(events[index][1].end.offset - events[index][1].start.offset) % 3 && |
|
!( |
|
(events[open][1].end.offset - |
|
events[open][1].start.offset + |
|
events[index][1].end.offset - |
|
events[index][1].start.offset) % |
|
3 |
|
) |
|
) { |
|
continue |
|
} |
|
|
|
// Number of markers to use from the sequence. |
|
use = |
|
events[open][1].end.offset - events[open][1].start.offset > 1 && |
|
events[index][1].end.offset - events[index][1].start.offset > 1 |
|
? 2 |
|
: 1 |
|
|
|
openingSequence = { |
|
type: use > 1 ? types.strongSequence : types.emphasisSequence, |
|
start: movePoint(shallow(events[open][1].end), -use), |
|
end: shallow(events[open][1].end) |
|
} |
|
closingSequence = { |
|
type: use > 1 ? types.strongSequence : types.emphasisSequence, |
|
start: shallow(events[index][1].start), |
|
end: movePoint(shallow(events[index][1].start), use) |
|
} |
|
text = { |
|
type: use > 1 ? types.strongText : types.emphasisText, |
|
start: shallow(events[open][1].end), |
|
end: shallow(events[index][1].start) |
|
} |
|
group = { |
|
type: use > 1 ? types.strong : types.emphasis, |
|
start: shallow(openingSequence.start), |
|
end: shallow(closingSequence.end) |
|
} |
|
|
|
events[open][1].end = shallow(openingSequence.start) |
|
events[index][1].start = shallow(closingSequence.end) |
|
|
|
nextEvents = [] |
|
|
|
// If there are more markers in the opening, add them before. |
|
if (events[open][1].end.offset - events[open][1].start.offset) { |
|
nextEvents = chunkedPush(nextEvents, [ |
|
['enter', events[open][1], context], |
|
['exit', events[open][1], context] |
|
]) |
|
} |
|
|
|
// Opening. |
|
nextEvents = chunkedPush(nextEvents, [ |
|
['enter', group, context], |
|
['enter', openingSequence, context], |
|
['exit', openingSequence, context], |
|
['enter', text, context] |
|
]) |
|
|
|
// Between. |
|
nextEvents = chunkedPush( |
|
nextEvents, |
|
resolveAll( |
|
context.parser.constructs.insideSpan.null, |
|
events.slice(open + 1, index), |
|
context |
|
) |
|
) |
|
|
|
// Closing. |
|
nextEvents = chunkedPush(nextEvents, [ |
|
['exit', text, context], |
|
['enter', closingSequence, context], |
|
['exit', closingSequence, context], |
|
['exit', group, context] |
|
]) |
|
|
|
// If there are more markers in the closing, add them after. |
|
if (events[index][1].end.offset - events[index][1].start.offset) { |
|
offset = 2 |
|
nextEvents = chunkedPush(nextEvents, [ |
|
['enter', events[index][1], context], |
|
['exit', events[index][1], context] |
|
]) |
|
} else { |
|
offset = 0 |
|
} |
|
|
|
chunkedSplice(events, open - 1, index - open + 3, nextEvents) |
|
|
|
index = open + nextEvents.length - offset - 2 |
|
break |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Remove remaining sequences. |
|
index = -1 |
|
|
|
while (++index < events.length) { |
|
if (events[index][1].type === 'attentionSequence') { |
|
events[index][1].type = 'data' |
|
} |
|
} |
|
|
|
return events |
|
} |
|
|
|
function tokenizeAttention(effects, ok) { |
|
var before = classifyCharacter(this.previous) |
|
var marker |
|
|
|
return start |
|
|
|
function start(code) { |
|
assert( |
|
code === codes.asterisk || code === codes.underscore, |
|
'expected asterisk or underscore' |
|
) |
|
effects.enter('attentionSequence') |
|
marker = code |
|
return sequence(code) |
|
} |
|
|
|
function sequence(code) { |
|
var token |
|
var after |
|
var open |
|
var close |
|
|
|
if (code === marker) { |
|
effects.consume(code) |
|
return sequence |
|
} |
|
|
|
token = effects.exit('attentionSequence') |
|
after = classifyCharacter(code) |
|
open = !after || (after === constants.characterGroupPunctuation && before) |
|
close = !before || (before === constants.characterGroupPunctuation && after) |
|
token._open = marker === codes.asterisk ? open : open && (before || !close) |
|
token._close = marker === codes.asterisk ? close : close && (after || !open) |
|
return ok(code) |
|
} |
|
}
|
|
|