label-end.mjs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. var labelEnd = {
  2. name: 'labelEnd',
  3. tokenize: tokenizeLabelEnd,
  4. resolveTo: resolveToLabelEnd,
  5. resolveAll: resolveAllLabelEnd
  6. }
  7. export default labelEnd
  8. import assert from 'assert'
  9. import codes from '../character/codes.mjs'
  10. import markdownLineEndingOrSpace from '../character/markdown-line-ending-or-space.mjs'
  11. import constants from '../constant/constants.mjs'
  12. import types from '../constant/types.mjs'
  13. import chunkedPush from '../util/chunked-push.mjs'
  14. import chunkedSplice from '../util/chunked-splice.mjs'
  15. import normalizeIdentifier from '../util/normalize-identifier.mjs'
  16. import resolveAll from '../util/resolve-all.mjs'
  17. import shallow from '../util/shallow.mjs'
  18. import destinationFactory from './factory-destination.mjs'
  19. import labelFactory from './factory-label.mjs'
  20. import titleFactory from './factory-title.mjs'
  21. import whitespaceFactory from './factory-whitespace.mjs'
  22. var resourceConstruct = {tokenize: tokenizeResource}
  23. var fullReferenceConstruct = {tokenize: tokenizeFullReference}
  24. var collapsedReferenceConstruct = {tokenize: tokenizeCollapsedReference}
  25. function resolveAllLabelEnd(events) {
  26. var index = -1
  27. var token
  28. while (++index < events.length) {
  29. token = events[index][1]
  30. if (
  31. !token._used &&
  32. (token.type === types.labelImage ||
  33. token.type === types.labelLink ||
  34. token.type === types.labelEnd)
  35. ) {
  36. // Remove the marker.
  37. events.splice(index + 1, token.type === types.labelImage ? 4 : 2)
  38. token.type = types.data
  39. index++
  40. }
  41. }
  42. return events
  43. }
  44. function resolveToLabelEnd(events, context) {
  45. var index = events.length
  46. var offset = 0
  47. var group
  48. var label
  49. var text
  50. var token
  51. var open
  52. var close
  53. var media
  54. // Find an opening.
  55. while (index--) {
  56. token = events[index][1]
  57. if (open) {
  58. // If we see another link, or inactive link label, we’ve been here before.
  59. if (
  60. token.type === types.link ||
  61. (token.type === types.labelLink && token._inactive)
  62. ) {
  63. break
  64. }
  65. // Mark other link openings as inactive, as we can’t have links in
  66. // links.
  67. if (events[index][0] === 'enter' && token.type === types.labelLink) {
  68. token._inactive = true
  69. }
  70. } else if (close) {
  71. if (
  72. events[index][0] === 'enter' &&
  73. (token.type === types.labelImage || token.type === types.labelLink) &&
  74. !token._balanced
  75. ) {
  76. open = index
  77. if (token.type !== types.labelLink) {
  78. offset = 2
  79. break
  80. }
  81. }
  82. } else if (token.type === types.labelEnd) {
  83. close = index
  84. }
  85. }
  86. group = {
  87. type: events[open][1].type === types.labelLink ? types.link : types.image,
  88. start: shallow(events[open][1].start),
  89. end: shallow(events[events.length - 1][1].end)
  90. }
  91. label = {
  92. type: types.label,
  93. start: shallow(events[open][1].start),
  94. end: shallow(events[close][1].end)
  95. }
  96. text = {
  97. type: types.labelText,
  98. start: shallow(events[open + offset + 2][1].end),
  99. end: shallow(events[close - 2][1].start)
  100. }
  101. media = [
  102. ['enter', group, context],
  103. ['enter', label, context]
  104. ]
  105. // Opening marker.
  106. media = chunkedPush(media, events.slice(open + 1, open + offset + 3))
  107. // Text open.
  108. media = chunkedPush(media, [['enter', text, context]])
  109. // Between.
  110. media = chunkedPush(
  111. media,
  112. resolveAll(
  113. context.parser.constructs.insideSpan.null,
  114. events.slice(open + offset + 4, close - 3),
  115. context
  116. )
  117. )
  118. // Text close, marker close, label close.
  119. media = chunkedPush(media, [
  120. ['exit', text, context],
  121. events[close - 2],
  122. events[close - 1],
  123. ['exit', label, context]
  124. ])
  125. // Reference, resource, or so.
  126. media = chunkedPush(media, events.slice(close + 1))
  127. // Media close.
  128. media = chunkedPush(media, [['exit', group, context]])
  129. chunkedSplice(events, open, events.length, media)
  130. return events
  131. }
  132. function tokenizeLabelEnd(effects, ok, nok) {
  133. var self = this
  134. var index = self.events.length
  135. var labelStart
  136. var defined
  137. // Find an opening.
  138. while (index--) {
  139. if (
  140. (self.events[index][1].type === types.labelImage ||
  141. self.events[index][1].type === types.labelLink) &&
  142. !self.events[index][1]._balanced
  143. ) {
  144. labelStart = self.events[index][1]
  145. break
  146. }
  147. }
  148. return start
  149. function start(code) {
  150. assert(code === codes.rightSquareBracket, 'expected `]`')
  151. if (!labelStart) {
  152. return nok(code)
  153. }
  154. // It’s a balanced bracket, but contains a link.
  155. if (labelStart._inactive) return balanced(code)
  156. defined =
  157. self.parser.defined.indexOf(
  158. normalizeIdentifier(
  159. self.sliceSerialize({start: labelStart.end, end: self.now()})
  160. )
  161. ) > -1
  162. effects.enter(types.labelEnd)
  163. effects.enter(types.labelMarker)
  164. effects.consume(code)
  165. effects.exit(types.labelMarker)
  166. effects.exit(types.labelEnd)
  167. return afterLabelEnd
  168. }
  169. function afterLabelEnd(code) {
  170. // Resource: `[asd](fgh)`.
  171. if (code === codes.leftParenthesis) {
  172. return effects.attempt(
  173. resourceConstruct,
  174. ok,
  175. defined ? ok : balanced
  176. )(code)
  177. }
  178. // Collapsed (`[asd][]`) or full (`[asd][fgh]`) reference?
  179. if (code === codes.leftSquareBracket) {
  180. return effects.attempt(
  181. fullReferenceConstruct,
  182. ok,
  183. defined
  184. ? effects.attempt(collapsedReferenceConstruct, ok, balanced)
  185. : balanced
  186. )(code)
  187. }
  188. // Shortcut reference: `[asd]`?
  189. return defined ? ok(code) : balanced(code)
  190. }
  191. function balanced(code) {
  192. labelStart._balanced = true
  193. return nok(code)
  194. }
  195. }
  196. function tokenizeResource(effects, ok, nok) {
  197. return start
  198. function start(code) {
  199. assert.equal(code, codes.leftParenthesis, 'expected left paren')
  200. effects.enter(types.resource)
  201. effects.enter(types.resourceMarker)
  202. effects.consume(code)
  203. effects.exit(types.resourceMarker)
  204. return whitespaceFactory(effects, open)
  205. }
  206. function open(code) {
  207. if (code === codes.rightParenthesis) {
  208. return end(code)
  209. }
  210. return destinationFactory(
  211. effects,
  212. destinationAfter,
  213. nok,
  214. types.resourceDestination,
  215. types.resourceDestinationLiteral,
  216. types.resourceDestinationLiteralMarker,
  217. types.resourceDestinationRaw,
  218. types.resourceDestinationString,
  219. constants.linkResourceDestinationBalanceMax
  220. )(code)
  221. }
  222. function destinationAfter(code) {
  223. return markdownLineEndingOrSpace(code)
  224. ? whitespaceFactory(effects, between)(code)
  225. : end(code)
  226. }
  227. function between(code) {
  228. if (
  229. code === codes.quotationMark ||
  230. code === codes.apostrophe ||
  231. code === codes.leftParenthesis
  232. ) {
  233. return titleFactory(
  234. effects,
  235. whitespaceFactory(effects, end),
  236. nok,
  237. types.resourceTitle,
  238. types.resourceTitleMarker,
  239. types.resourceTitleString
  240. )(code)
  241. }
  242. return end(code)
  243. }
  244. function end(code) {
  245. if (code === codes.rightParenthesis) {
  246. effects.enter(types.resourceMarker)
  247. effects.consume(code)
  248. effects.exit(types.resourceMarker)
  249. effects.exit(types.resource)
  250. return ok
  251. }
  252. return nok(code)
  253. }
  254. }
  255. function tokenizeFullReference(effects, ok, nok) {
  256. var self = this
  257. return start
  258. function start(code) {
  259. assert.equal(code, codes.leftSquareBracket, 'expected left bracket')
  260. return labelFactory.call(
  261. self,
  262. effects,
  263. afterLabel,
  264. nok,
  265. types.reference,
  266. types.referenceMarker,
  267. types.referenceString
  268. )(code)
  269. }
  270. function afterLabel(code) {
  271. return self.parser.defined.indexOf(
  272. normalizeIdentifier(
  273. self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1)
  274. )
  275. ) < 0
  276. ? nok(code)
  277. : ok(code)
  278. }
  279. }
  280. function tokenizeCollapsedReference(effects, ok, nok) {
  281. return start
  282. function start(code) {
  283. assert.equal(code, codes.leftSquareBracket, 'expected left bracket')
  284. effects.enter(types.reference)
  285. effects.enter(types.referenceMarker)
  286. effects.consume(code)
  287. effects.exit(types.referenceMarker)
  288. return open
  289. }
  290. function open(code) {
  291. if (code === codes.rightSquareBracket) {
  292. effects.enter(types.referenceMarker)
  293. effects.consume(code)
  294. effects.exit(types.referenceMarker)
  295. effects.exit(types.reference)
  296. return ok
  297. }
  298. return nok(code)
  299. }
  300. }