attention.mjs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. var attention = {
  2. name: 'attention',
  3. tokenize: tokenizeAttention,
  4. resolveAll: resolveAllAttention
  5. }
  6. export default attention
  7. import assert from 'assert'
  8. import codes from '../character/codes.mjs'
  9. import constants from '../constant/constants.mjs'
  10. import types from '../constant/types.mjs'
  11. import chunkedPush from '../util/chunked-push.mjs'
  12. import chunkedSplice from '../util/chunked-splice.mjs'
  13. import classifyCharacter from '../util/classify-character.mjs'
  14. import movePoint from '../util/move-point.mjs'
  15. import resolveAll from '../util/resolve-all.mjs'
  16. import shallow from '../util/shallow.mjs'
  17. // Take all events and resolve attention to emphasis or strong.
  18. function resolveAllAttention(events, context) {
  19. var index = -1
  20. var open
  21. var group
  22. var text
  23. var openingSequence
  24. var closingSequence
  25. var use
  26. var nextEvents
  27. var offset
  28. // Walk through all events.
  29. //
  30. // Note: performance of this is fine on an mb of normal markdown, but it’s
  31. // a bottleneck for malicious stuff.
  32. while (++index < events.length) {
  33. // Find a token that can close.
  34. if (
  35. events[index][0] === 'enter' &&
  36. events[index][1].type === 'attentionSequence' &&
  37. events[index][1]._close
  38. ) {
  39. open = index
  40. // Now walk back to find an opener.
  41. while (open--) {
  42. // Find a token that can open the closer.
  43. if (
  44. events[open][0] === 'exit' &&
  45. events[open][1].type === 'attentionSequence' &&
  46. events[open][1]._open &&
  47. // If the markers are the same:
  48. context.sliceSerialize(events[open][1]).charCodeAt(0) ===
  49. context.sliceSerialize(events[index][1]).charCodeAt(0)
  50. ) {
  51. // If the opening can close or the closing can open,
  52. // and the close size *is not* a multiple of three,
  53. // but the sum of the opening and closing size *is* multiple of three,
  54. // then don’t match.
  55. if (
  56. (events[open][1]._close || events[index][1]._open) &&
  57. (events[index][1].end.offset - events[index][1].start.offset) % 3 &&
  58. !(
  59. (events[open][1].end.offset -
  60. events[open][1].start.offset +
  61. events[index][1].end.offset -
  62. events[index][1].start.offset) %
  63. 3
  64. )
  65. ) {
  66. continue
  67. }
  68. // Number of markers to use from the sequence.
  69. use =
  70. events[open][1].end.offset - events[open][1].start.offset > 1 &&
  71. events[index][1].end.offset - events[index][1].start.offset > 1
  72. ? 2
  73. : 1
  74. openingSequence = {
  75. type: use > 1 ? types.strongSequence : types.emphasisSequence,
  76. start: movePoint(shallow(events[open][1].end), -use),
  77. end: shallow(events[open][1].end)
  78. }
  79. closingSequence = {
  80. type: use > 1 ? types.strongSequence : types.emphasisSequence,
  81. start: shallow(events[index][1].start),
  82. end: movePoint(shallow(events[index][1].start), use)
  83. }
  84. text = {
  85. type: use > 1 ? types.strongText : types.emphasisText,
  86. start: shallow(events[open][1].end),
  87. end: shallow(events[index][1].start)
  88. }
  89. group = {
  90. type: use > 1 ? types.strong : types.emphasis,
  91. start: shallow(openingSequence.start),
  92. end: shallow(closingSequence.end)
  93. }
  94. events[open][1].end = shallow(openingSequence.start)
  95. events[index][1].start = shallow(closingSequence.end)
  96. nextEvents = []
  97. // If there are more markers in the opening, add them before.
  98. if (events[open][1].end.offset - events[open][1].start.offset) {
  99. nextEvents = chunkedPush(nextEvents, [
  100. ['enter', events[open][1], context],
  101. ['exit', events[open][1], context]
  102. ])
  103. }
  104. // Opening.
  105. nextEvents = chunkedPush(nextEvents, [
  106. ['enter', group, context],
  107. ['enter', openingSequence, context],
  108. ['exit', openingSequence, context],
  109. ['enter', text, context]
  110. ])
  111. // Between.
  112. nextEvents = chunkedPush(
  113. nextEvents,
  114. resolveAll(
  115. context.parser.constructs.insideSpan.null,
  116. events.slice(open + 1, index),
  117. context
  118. )
  119. )
  120. // Closing.
  121. nextEvents = chunkedPush(nextEvents, [
  122. ['exit', text, context],
  123. ['enter', closingSequence, context],
  124. ['exit', closingSequence, context],
  125. ['exit', group, context]
  126. ])
  127. // If there are more markers in the closing, add them after.
  128. if (events[index][1].end.offset - events[index][1].start.offset) {
  129. offset = 2
  130. nextEvents = chunkedPush(nextEvents, [
  131. ['enter', events[index][1], context],
  132. ['exit', events[index][1], context]
  133. ])
  134. } else {
  135. offset = 0
  136. }
  137. chunkedSplice(events, open - 1, index - open + 3, nextEvents)
  138. index = open + nextEvents.length - offset - 2
  139. break
  140. }
  141. }
  142. }
  143. }
  144. // Remove remaining sequences.
  145. index = -1
  146. while (++index < events.length) {
  147. if (events[index][1].type === 'attentionSequence') {
  148. events[index][1].type = 'data'
  149. }
  150. }
  151. return events
  152. }
  153. function tokenizeAttention(effects, ok) {
  154. var before = classifyCharacter(this.previous)
  155. var marker
  156. return start
  157. function start(code) {
  158. assert(
  159. code === codes.asterisk || code === codes.underscore,
  160. 'expected asterisk or underscore'
  161. )
  162. effects.enter('attentionSequence')
  163. marker = code
  164. return sequence(code)
  165. }
  166. function sequence(code) {
  167. var token
  168. var after
  169. var open
  170. var close
  171. if (code === marker) {
  172. effects.consume(code)
  173. return sequence
  174. }
  175. token = effects.exit('attentionSequence')
  176. after = classifyCharacter(code)
  177. open = !after || (after === constants.characterGroupPunctuation && before)
  178. close = !before || (before === constants.characterGroupPunctuation && after)
  179. token._open = marker === codes.asterisk ? open : open && (before || !close)
  180. token._close = marker === codes.asterisk ? close : close && (after || !open)
  181. return ok(code)
  182. }
  183. }