samemark 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/block.js ADDED
@@ -0,0 +1,1807 @@
1
+ // Hand-written CommonMark + GFM block parser producing mdast (block structure only).
2
+ // Phrasing containers (paragraph, heading, tableCell) get children: [].
3
+ import {normalizeIdentifier} from 'micromark-util-normalize-identifier'
4
+ import {decodeString} from 'micromark-util-decode-string'
5
+ import {htmlBlockNames} from 'micromark-util-html-tag-name'
6
+ import {inlineSingle, inlineMulti, setSource, setDefinitions, setMath, setTrackDefs, takeNeeds} from './inline.js'
7
+
8
+ const BLOCK_NAMES = new Set(htmlBlockNames)
9
+
10
+ const ROOT = 0, BQ = 1, LIST = 2, ITEM = 3, FN = 4
11
+ const NONE = 0, PARA = 1, ICODE = 2, FCODE = 3, HTML = 4, TABLE = 5
12
+
13
+ let src = '', len = 0, hasCR = false
14
+ let ls = 0, le = 0, eolLen = 0, lineNo = 0
15
+ let pos = 0, col = 0, vleft = 0
16
+ let stk = [], sp = 0
17
+ let ns = 0, indent = 0, blank = false
18
+ let e1Line = 1, e1Ls = 0, e1Off = 0
19
+ let lineTok = false, lineCont = false, lastBq = 0, bqIdx = 0
20
+ let e2Line = 1, e2Ls = 0, e2Off = 0, ebLine = 0, ebLs = 0, ebOff = 0, ebBi = 0
21
+ let pendingBlank = false, pbCi = 0, pbBq = false
22
+ let failKind = -1
23
+ let root = null
24
+
25
+ // leaf state
26
+ let leaf = NONE
27
+ // paragraph
28
+ let pL = 0, pLs = 0, pOff = 0, eL = 0, eLs = 0, eOff = 0, pCand = false
29
+ let pLines = [], pPos = [], pVl = []
30
+ // phrasing containers whose children are filled in after the block pass (definitions may follow use)
31
+ let emptyItemLine = -1
32
+ // streaming bookkeeping (parseStream)
33
+ let streamOn = false, streamSafe = null, streamEntries = []
34
+ const defLog = [], fnLog = [], rcLog = []
35
+ let initDefs = null, initFns = null
36
+ function topNow() { return root.children.length - 1 }
37
+ let mathOn = false
38
+ // indented code that started on a line that closed containers (micromark: its flow is not `interrupt`ible)
39
+ let icClosed = false
40
+ let pend = [], defIds = new Set(), fnIds = new Set(), doInline = true
41
+ // code / html
42
+ let cL = 0, cLs = 0, cOff = 0, cVal = '', cN = 0, cLastL = 0, cLastLs = 0, cLastLe = 0, cEol = ''
43
+ let fEolCR = false
44
+ let fch = 0, flen = 0, findent = 0, flang = null, fmeta = null
45
+ let hkind = 0
46
+ let icPend = ''
47
+ // table
48
+ let tNode = null, tState = 0, tSize = 0
49
+
50
+ function P(line, lsv, off) {
51
+ return {line, column: off - lsv + 1, offset: off}
52
+ }
53
+
54
+ function cont(kind, node) {
55
+ return {kind, node, cc: 0, sb: false, fb: false, ord: false, mk: 0}
56
+ }
57
+
58
+ // ---- cursor helpers ----
59
+ function findNS() {
60
+ let i = pos
61
+ let c = col + vleft
62
+ const base = col
63
+ while (i < le) {
64
+ const ch = src.charCodeAt(i)
65
+ if (ch === 32) { i++; c++ } else if (ch === 9) { c += 4 - (c & 3); i++ } else break
66
+ }
67
+ ns = i
68
+ indent = c - base
69
+ blank = i >= le
70
+ }
71
+
72
+ // like findNS but stops once `lim` columns of whitespace were seen (keeps per-container
73
+ // matching O(consumed columns) instead of rescanning the whole indentation run).
74
+ // When truncated, `blank` is reported false even if only whitespace follows.
75
+ function findNSL(lim) {
76
+ let i = pos
77
+ let c = col + vleft
78
+ const base = col
79
+ while (i < le && c - base < lim) {
80
+ const ch = src.charCodeAt(i)
81
+ if (ch === 32) { i++; c++ } else if (ch === 9) { c += 4 - (c & 3); i++ } else break
82
+ }
83
+ ns = i
84
+ indent = c - base
85
+ blank = i >= le
86
+ }
87
+
88
+ // consume n virtual columns
89
+ function advance(n) {
90
+ while (n > 0) {
91
+ if (vleft > 0) {
92
+ const t = vleft < n ? vleft : n
93
+ vleft -= t; col += t; n -= t
94
+ continue
95
+ }
96
+ if (src.charCodeAt(pos) === 9) {
97
+ const w = 4 - (col & 3)
98
+ pos++
99
+ if (n >= w) { col += w; n -= w } else { vleft = w - n; col += n; n = 0 }
100
+ } else {
101
+ pos++; col++; n--
102
+ }
103
+ }
104
+ }
105
+
106
+ function eolStr(at, n) {
107
+ if (n === 1) return src.charCodeAt(at) === 10 ? '\n' : '\r'
108
+ return n === 2 ? '\r\n' : ''
109
+ }
110
+
111
+ function restOfLine() {
112
+ return vleft > 0 ? ' '.repeat(vleft) + src.slice(pos, le) : src.slice(pos, le)
113
+ }
114
+
115
+ // ---- container closing ----
116
+ let listSeen = false
117
+ function closeContainers(to) {
118
+ listSeen = false
119
+ while (sp > to) {
120
+ const c = stk[--sp]
121
+ stk[sp] = null
122
+ if (c.kind === ITEM) {
123
+ const ch = c.node.children
124
+ const last = ch.length ? ch[ch.length - 1].type : ''
125
+ // closed right after a container prefix: an item that is empty or ends in a container exits there too
126
+ c.node.position.end = closeOver && (closeFn || last === 'list' || last === 'blockquote' || (e12Line === lineNo && (last === 'code' || last === 'html' || last === 'math'))) ? closeOver() : last === 'list' && e1Line > e2Line && bqRun > 0 ? P(e1Line, e1Ls, e1Off) : pickE2(sp)
127
+ } else {
128
+ c.node.position.end = P(e1Line, e1Ls, e1Off)
129
+ if (c.kind === LIST) { if (bqRun >= 2 && !listSeen) c.node.spread = true; listSeen = true }
130
+ }
131
+ }
132
+ }
133
+
134
+ function setE1(l, lsv, off) { e1Line = l; e1Ls = lsv; e1Off = off }
135
+ // the last token of an unclosed fence / html block is the code itself: containers end where it ends
136
+ let cRealL = 0, cRealLs = 0, cRealLe = 0
137
+ function setE12(end) {
138
+ if (end.line > cRealL) e12Line = lineNo
139
+ if (cRealLe > end.offset) end = P(cRealL, cRealLs, cRealLe)
140
+ const lsv = end.offset - end.column + 1
141
+ e1Line = end.line; e1Ls = lsv; e1Off = end.offset
142
+ e2Line = end.line; e2Ls = lsv; e2Off = end.offset
143
+ }
144
+
145
+ // ---- leaf closing ----
146
+ function parentChildren() { return stk[sp - 1].node.children }
147
+
148
+ function closeLeaf(eolIncluded) {
149
+ switch (leaf) {
150
+ case NONE: return
151
+ case PARA: leaf = NONE; finishParagraph(-1); return
152
+ case ICODE: leaf = NONE; icPend = ''; finishICode(); return
153
+ case FCODE: leaf = NONE; finishFCode(false, eolIncluded); return
154
+ case HTML: leaf = NONE; finishHtml(eolIncluded); return
155
+ case TABLE: leaf = NONE; finishTable(); return
156
+ }
157
+ }
158
+
159
+ function finishICode() {
160
+ let v = cVal
161
+ const lc = v.charCodeAt(v.length - 1)
162
+ if (lc === 10) v = v.slice(0, v.length - (v.charCodeAt(v.length - 2) === 13 ? 2 : 1))
163
+ else if (lc === 13) v = v.slice(0, -1)
164
+ const node = {
165
+ type: 'code', lang: null, meta: null, value: v,
166
+ position: {start: P(cL, cLs, cOff), end: P(cLastL, cLastLs, cLastLe)}
167
+ }
168
+ parentChildren().push(node)
169
+ }
170
+
171
+ function endOfCode(eolIncluded, eof) {
172
+ if (eolIncluded && eof) {
173
+ // end of document
174
+ if (eolLenAtEof > 0) return P(lineNo + 1, len, len)
175
+ return P(cLastL, cLastLs, cLastLe)
176
+ }
177
+ if (eolIncluded) return P(lineNo, ls, ls)
178
+ return P(cLastL, cLastLs, cLastLe)
179
+ }
180
+ let eolLenAtEof = 0
181
+
182
+ function finishFCode(closed, eolIncluded, eof) {
183
+ let val = cVal
184
+ let end
185
+ if (closed) {
186
+ end = P(cLastL, cLastLs, cLastLe)
187
+ } else {
188
+ end = endOfCode(eolIncluded, eof)
189
+ if (!eolIncluded) {
190
+ const c = val.charCodeAt(val.length - 1)
191
+ if (c === 10) val = val.slice(0, val.length - (val.charCodeAt(val.length - 2) === 13 ? 2 : 1))
192
+ else if (c === 13) val = val.slice(0, -1)
193
+ }
194
+ setE12(end)
195
+ }
196
+ // micromark strips `^(\r?\n|\r)` from the data: a lone-CR opening EOL followed by an LF-terminated empty first line
197
+ // reads as one CRLF; the same at the end (content ending in CR, then the LF before the closing fence)
198
+ if (fEolCR && val.charCodeAt(0) === 10) val = val.slice(1)
199
+ if (closed && val.charCodeAt(val.length - 1) === 13 && cEol === '\n') val = val.slice(0, -1)
200
+ if (fch === 36) {
201
+ parentChildren().push({
202
+ type: 'math', meta: fmeta, value: val,
203
+ data: {hName: 'pre', hChildren: [{type: 'element', tagName: 'code', properties: {className: ['language-math', 'math-display']}, children: [{type: 'text', value: val}]}]},
204
+ position: {start: P(cL, cLs, cOff), end}
205
+ })
206
+ return
207
+ }
208
+ parentChildren().push({
209
+ type: 'code', lang: flang, meta: fmeta, value: val,
210
+ position: {start: P(cL, cLs, cOff), end}
211
+ })
212
+ }
213
+
214
+ function finishHtml(eolIncluded, eof) {
215
+ let val = cVal
216
+ let end
217
+ if (eolIncluded && hkind <= 5) {
218
+ end = endOfCode(true, eof)
219
+ if (end.offset !== cLastLe) {
220
+ val += cEolHtml()
221
+ setE12(end)
222
+ }
223
+ } else end = P(cLastL, cLastLs, cLastLe)
224
+ parentChildren().push({
225
+ type: 'html', value: val,
226
+ position: {start: P(cL, cLs, cOff), end}
227
+ })
228
+ }
229
+
230
+ function finishTable() {
231
+ tNode = null
232
+ }
233
+
234
+ // ---- paragraph / definitions ----
235
+ function isWs(c) { return c === 32 || c === 9 }
236
+
237
+ function paragraphHasContentAfterDefs() {
238
+ if (!pCand) return true
239
+ return finishParagraph(-2)
240
+ }
241
+
242
+ // Is there a ']:' in [from, to)? Cached next-occurrence so the total scan is
243
+ // linear over the document (paragraphs are visited in increasing offset order).
244
+ let dm = -2
245
+ function defMarkerIn(from, to) {
246
+ if (dm === -2 || (dm !== -1 && dm < from)) dm = src.indexOf(']:', from)
247
+ return dm !== -1 && dm < to
248
+ }
249
+
250
+ // mode -1: emit; -2: dry-run returning whether paragraph content remains
251
+ function finishParagraph(mode, heading) {
252
+ let startL = pL, startLs = pLs, startOff = pOff
253
+ const out = mode === -2 ? null : parentChildren()
254
+ let remainingFrom = 0 // index of first remaining line in pLines (4 numbers per line)
255
+ const nBefore = out === null ? 0 : out.length
256
+ if (pCand && defMarkerIn(pOff, eOff)) {
257
+ const r = extractDefs(out)
258
+ if (r === -1) { // all consumed
259
+ if (mode === -2) return false
260
+ pLines.length = 0; pPos.length = 0; pVl.length = 0
261
+ return false
262
+ }
263
+ remainingFrom = r
264
+ if (r > 0) {
265
+ startL = pLines[r]; startLs = pLines[r + 1]; startOff = pLines[r + 2]
266
+ }
267
+ }
268
+ if (mode === -2) return true
269
+ const posn = {start: P(startL, startLs, startOff), end: undefined}
270
+ let node
271
+ let task = false
272
+ if (heading) {
273
+ posn.start = P(pL, pLs, pOff)
274
+ posn.end = heading.end
275
+ node = {type: 'heading', depth: heading.depth, children: [], position: posn}
276
+ out.push(node)
277
+ } else {
278
+ posn.end = P(eL, eLs, eOff)
279
+ const parent = stk[sp - 1]
280
+ // extra whitespace before the content on a later line is a linePrefix token: micromark then does not
281
+ // treat the text as the first content of the item
282
+ const fi = remainingFrom >> 2
283
+ const prefixed = parent.kind === ITEM && startL > parent.node.position.start.line && (startOff > pPos[fi] || pVl[fi] > 0)
284
+ // (definitions at the start of the same content do not count: the text after them still starts the chunk)
285
+ if (parent.kind === ITEM && nBefore === 0 && src.charCodeAt(startOff) === 91 && !prefixed) {
286
+ const t = taskCheck(startOff, startLs)
287
+ if (t !== 0) {
288
+ parent.node.checked = t === 1
289
+ task = taskLenOut === 4 ? 11 : 1
290
+ } else task = 3 // candidate: micromark attempts a task check here (matters for EOL positions)
291
+ } else if (startL === emptyItemLine + 1 && src.charCodeAt(startOff) === 91 && startOff === pPos[fi] && pVl[fi] === 0 &&
292
+ !(parent.node.position && parent.node.position.start.line === startL) && !(parent.kind === ITEM && parent.node.position.start.line === emptyItemLine)) {
293
+ // micromark quirk: right after an empty list item (marker directly followed by the EOL) the next
294
+ // paragraph is tokenised as if it were the first content of that item; `checked` lands on its parent
295
+ const t = taskCheck(startOff, startLs)
296
+ if (t !== 0) {
297
+ parent.node.checked = t === 1
298
+ if (streamOn && parent.kind === ROOT) rcLog.push(root.children.length, t === 1)
299
+ task = taskLenOut === 4 ? 12 : 2
300
+ }
301
+ }
302
+ node = {type: 'paragraph', children: [], position: posn}
303
+ out.push(node)
304
+ }
305
+ if (doInline) {
306
+ const nl = (pLines.length - remainingFrom) >> 2
307
+ if (nl === 1) {
308
+ pend.push(node, task, pLines[remainingFrom], pLines[remainingFrom + 1], pLines[remainingFrom + 2], pLines[remainingFrom + 3], null, topNow())
309
+ } else {
310
+ const lines = new Array(nl * 5)
311
+ for (let i = 0; i < nl; i++) {
312
+ const a = remainingFrom + i * 4
313
+ lines[i * 5] = pLines[a]; lines[i * 5 + 1] = pLines[a + 1]
314
+ lines[i * 5 + 2] = i === 0 ? pLines[a + 2] : pPos[(a >> 2)]
315
+ lines[i * 5 + 3] = pLines[a + 3]
316
+ lines[i * 5 + 4] = i === 0 ? 0 : pVl[(a >> 2)]
317
+ }
318
+ pend.push(node, task, 0, 0, 0, 0, lines, topNow())
319
+ }
320
+ }
321
+ pLines.length = 0; pPos.length = 0; pVl.length = 0
322
+ return true
323
+ }
324
+
325
+ // returns 0 none, 1 checked, 2 unchecked
326
+ let taskLenOut = 3
327
+ function taskCheck(o, ls0) {
328
+ const c1 = src.charCodeAt(o + 1)
329
+ let checked
330
+ taskLenOut = 3
331
+ if (c1 === 120 || c1 === 88) checked = 1
332
+ else if (c1 === 32 || c1 === 10 || c1 === 13) checked = 2
333
+ else if (c1 === 9) {
334
+ // a tab as the value is followed by its remaining virtual spaces unless it is one column wide
335
+ let col = 0
336
+ for (let q = ls0; q < o + 1; q++) col += src.charCodeAt(q) === 9 ? 4 - (col & 3) : 1
337
+ if (4 - (col & 3) !== 1) return 0
338
+ checked = 2
339
+ } else return 0
340
+ let close = o + 2
341
+ if (c1 === 13 && src.charCodeAt(o + 2) === 10) { close = o + 3; taskLenOut = 4 }
342
+ if (src.charCodeAt(close) !== 93) return 0
343
+ const q = close + 1 // after ]
344
+ const c = src.charCodeAt(q)
345
+ if (c === 10 || c === 13) return q < eOff ? checked : 0
346
+ if (c !== 32 && c !== 9) return 0
347
+ // whitespace then non-end
348
+ let i = q + 1
349
+ while (i < len) {
350
+ const d = src.charCodeAt(i)
351
+ if (d === 32 || d === 9) i++
352
+ else break
353
+ }
354
+ if (i >= eOff) return 0
355
+ return checked
356
+ }
357
+
358
+ // returns index into pLines of first remaining line, or -1 if all consumed
359
+ function extractDefs(out) {
360
+ const n = pLines.length / 4
361
+ // build text
362
+ let text = ''
363
+ const starts = new Array(n)
364
+ for (let i = 0; i < n; i++) {
365
+ starts[i] = text.length
366
+ const a = i === 0 ? pLines[2] : pPos[i] // continuation lines keep their indentation (label text)
367
+ text += (i ? '\n' : '') + src.slice(a, pLines[i * 4 + 3])
368
+ starts[i] = i ? text.length - (pLines[i * 4 + 3] - a) : 0
369
+ }
370
+ let li = 0 // current line index
371
+ const tl = text.length
372
+ while (li < n) {
373
+ let from = starts[li]
374
+ while (from < tl && isWs(text.charCodeAt(from))) from++
375
+ const r = parseDef(text, from, tl, starts, n, li)
376
+ if (!r) break
377
+ // r: {endIdx, endLine, label, ident, url, title}
378
+ const el = r.endLine
379
+ if (out) {
380
+ defIds.add(r.idU)
381
+ out.push({
382
+ type: 'definition', identifier: r.ident, label: r.label, title: r.title, url: r.url,
383
+ position: {
384
+ start: P(pLines[li * 4], pLines[li * 4 + 1], pLines[li * 4 + 2]),
385
+ end: P(pLines[el * 4], pLines[el * 4 + 1], pLines[el * 4 + 3])
386
+ }
387
+ })
388
+ if (streamOn) defLog.push(r.idU, topNow())
389
+ }
390
+ li = el + 1
391
+ }
392
+ if (li >= n) return -1
393
+ return li * 4
394
+ }
395
+
396
+ // with CR / CRLF line endings the definition text is joined with '\n'; the mdast values keep the raw EOLs
397
+ function lineOf(starts, n, idx) { let l = 0; while (l + 1 < n && starts[l + 1] <= idx) l++; return l }
398
+ function eolOfLine(L) {
399
+ const e = pLines[L * 4 + 3]
400
+ const c = src.charCodeAt(e)
401
+ return c === 13 ? (src.charCodeAt(e + 1) === 10 ? '\r\n' : '\r') : '\n'
402
+ }
403
+ function rawEols(str, starts, n, fromIdx) {
404
+ let l = lineOf(starts, n, fromIdx), out = '', a = 0
405
+ for (;;) {
406
+ const k = str.indexOf('\n', a)
407
+ if (k === -1) return out + str.slice(a)
408
+ out += str.slice(a, k) + eolOfLine(l++)
409
+ a = k + 1
410
+ }
411
+ }
412
+ function parseDef(text, from, tl, starts, n, li) {
413
+ let i = from
414
+ if (text.charCodeAt(i) !== 91) return null
415
+ // label
416
+ i++
417
+ const labelStart = i
418
+ let size = 0, hasData = false
419
+ for (;;) {
420
+ if (i >= tl) return null
421
+ const c = text.charCodeAt(i)
422
+ if (c === 91) return null
423
+ if (c === 93) break
424
+ if (size > 999) return null
425
+ if (c !== 32 && c !== 9 && c !== 10) hasData = true
426
+ size++
427
+ i++
428
+ if (c === 92) {
429
+ const d = text.charCodeAt(i)
430
+ if (d === 91 || d === 92 || d === 93) { i++; size++ }
431
+ }
432
+ }
433
+ if (!hasData) return null
434
+ const rawLabel = text.slice(labelStart, i)
435
+ i++
436
+ if (text.charCodeAt(i) !== 58) return null
437
+ i++
438
+ // whitespace with optional single eol
439
+ i = skipWsEol(text, i, tl)
440
+ // destination
441
+ let url
442
+ const c0 = text.charCodeAt(i)
443
+ if (c0 === 60) {
444
+ i++
445
+ const s = i
446
+ for (;;) {
447
+ if (i >= tl) return null
448
+ const c = text.charCodeAt(i)
449
+ if (c === 10 || c === 60) return null
450
+ if (c === 62) break
451
+ i++
452
+ if (c === 92) { const d = text.charCodeAt(i); if (d === 60 || d === 62 || d === 92) i++ }
453
+ }
454
+ url = text.slice(s, i)
455
+ i++
456
+ } else {
457
+ const s = i
458
+ let depth = 0
459
+ for (;;) {
460
+ if (i >= tl) break
461
+ const c = text.charCodeAt(i)
462
+ if (c === 40) { if (++depth > 32) return null }
463
+ else if (c === 41) { if (depth === 0) break; depth-- }
464
+ else if (c === 32 || c === 9 || c === 10 || c < 32 || c === 127) break
465
+ else if (c === 92) { const d = text.charCodeAt(i + 1); if (d === 40 || d === 41 || d === 92) i++ }
466
+ i++
467
+ }
468
+ if (depth !== 0) return null
469
+ if (i === s) return null
470
+ url = text.slice(s, i)
471
+ }
472
+ const afterDest = i
473
+ // try title
474
+ let title = null, titleStart = 0
475
+ let endIdx = -1
476
+ {
477
+ let j = afterDest
478
+ const j0 = j
479
+ j = skipWsEol(text, j, tl)
480
+ if (j > j0) {
481
+ const q = text.charCodeAt(j)
482
+ if (q === 34 || q === 39 || q === 40) {
483
+ const close = q === 40 ? 41 : q
484
+ let k = j + 1
485
+ let ok = false
486
+ for (;;) {
487
+ if (k >= tl) break
488
+ const c = text.charCodeAt(k)
489
+ if (c === close) { ok = true; break }
490
+ k++
491
+ if (c === 92) { const d = text.charCodeAt(k); if (d === q || d === close || d === 92) k++ }
492
+ }
493
+ if (ok) {
494
+ let m = k + 1
495
+ while (m < tl && isWs(text.charCodeAt(m))) m++
496
+ if (m >= tl || text.charCodeAt(m) === 10) {
497
+ title = k > j + 1 ? text.slice(j + 1, k) : null
498
+ titleStart = j + 1
499
+ endIdx = m
500
+ }
501
+ }
502
+ }
503
+ }
504
+ }
505
+ if (endIdx === -1) {
506
+ let m = afterDest
507
+ while (m < tl && isWs(text.charCodeAt(m))) m++
508
+ if (m >= tl || text.charCodeAt(m) === 10) endIdx = m
509
+ else return null
510
+ }
511
+ // find line of endIdx
512
+ let el = li
513
+ while (el + 1 < n && starts[el + 1] <= endIdx) el++
514
+ let label = rawLabel.indexOf('\\') === -1 && rawLabel.indexOf('&') === -1 ? rawLabel : decodeString(rawLabel)
515
+ if (hasCR && label.indexOf('\n') !== -1) label = rawEols(label, starts, n, labelStart)
516
+ const idU = normalizeIdentifier(rawLabel)
517
+ const ident = idU.toLowerCase()
518
+ if (url.indexOf('\\') !== -1 || url.indexOf('&') !== -1) url = decodeString(url)
519
+ if (title !== null) {
520
+ if (title.indexOf('\n') !== -1) { title = title.replace(/\n[ \t]*/g, '\n'); if (hasCR) title = rawEols(title, starts, n, titleStart) }
521
+ if (title.indexOf('\\') !== -1 || title.indexOf('&') !== -1) title = decodeString(title)
522
+ }
523
+ return {endIdx, endLine: el, label, ident, idU, url, title}
524
+ }
525
+
526
+ function skipWsEol(text, i, tl) {
527
+ while (i < tl && isWs(text.charCodeAt(i))) i++
528
+ if (text.charCodeAt(i) === 10) {
529
+ i++
530
+ while (i < tl && isWs(text.charCodeAt(i))) i++
531
+ }
532
+ return i
533
+ }
534
+
535
+ // ---- main ----
536
+ // remark-frontmatter / micromark-extension-frontmatter options -> [{type, open, close}] (same rules as toMatters)
537
+ const MARKERS = {yaml: '-', toml: '+'}
538
+ export function normalizeMatters(options) {
539
+ const list = Array.isArray(options) ? options : options ? [options] : ['yaml']
540
+ return list.map(o => {
541
+ let m = o
542
+ if (typeof m === 'string') {
543
+ if (!Object.prototype.hasOwnProperty.call(MARKERS, m)) throw new Error('Missing matter definition for `' + m + '`')
544
+ m = {type: m, marker: MARKERS[m]}
545
+ } else if (typeof m !== 'object' || m === null) throw new Error('Expected matter to be an object')
546
+ if (!('type' in m)) throw new Error('Missing `type` in matter')
547
+ const hasFence = 'fence' in m, hasMarker = 'marker' in m
548
+ if (!hasFence && !hasMarker) throw new Error('Missing `marker` or `fence` in matter')
549
+ const pick = (schema, prop) => typeof schema === 'string' ? schema : schema[prop]
550
+ const open = m.marker ? pick(m.marker, 'open').repeat(3) : pick(m.fence, 'open')
551
+ const close = m.marker ? pick(m.marker, 'close').repeat(3) : pick(m.fence, 'close')
552
+ return {type: m.type, open, close, anywhere: Boolean(m.anywhere)}
553
+ })
554
+ }
555
+
556
+ // true when samemark can parse these remark-frontmatter / micromark-extension-frontmatter options natively
557
+ // (everything except `anywhere: true`, which needs the real tokenizer)
558
+ export function frontmatterSupported(options) {
559
+ try {
560
+ const ms = normalizeMatters(options)
561
+ return ms.length > 0 && ms.every(m => !m.anywhere && m.open.length > 0 && m.close.length > 0)
562
+ } catch { return false }
563
+ }
564
+
565
+ // frontmatter at the very start of the document (micromark: flow construct, line 1 column 1, concrete).
566
+ // Returns true (state advanced to the closing fence line) or false.
567
+ let noContainers = false
568
+ function tryFrontmatter(matters) {
569
+ for (let mi = 0; mi < matters.length; mi++) {
570
+ const m = matters[mi]
571
+ const open = m.open
572
+ if (open.length === 0 || !src.startsWith(open, 0)) continue
573
+ let i = open.length
574
+ while (i < len) { const c = src.charCodeAt(i); if (c === 32 || c === 9) i++; else break }
575
+ if (i >= len) continue
576
+ let c = src.charCodeAt(i)
577
+ if (c !== 10 && c !== 13) continue
578
+ const eol1 = c === 13 && src.charCodeAt(i + 1) === 10 ? 2 : 1
579
+ const bodyStart = i + eol1
580
+ const close = m.close
581
+ // an open fence that never closes is still a pending `concrete` construct while the document is fed:
582
+ // no container (quote, list item, footnote definition) can start on any later line
583
+ noContainers = true
584
+ let pos = bodyStart, line = 2, prevEolLen = eol1
585
+ for (;;) {
586
+ if (pos >= len) break // the line after a trailing EOL is the end of input: not closed
587
+ // end of this line
588
+ let e = pos, eolL = 0
589
+ for (; e < len; e++) {
590
+ const d = src.charCodeAt(e)
591
+ if (d === 10) { eolL = 1; break }
592
+ if (d === 13) { eolL = src.charCodeAt(e + 1) === 10 ? 2 : 1; break }
593
+ }
594
+ // closing fence?
595
+ if (src.startsWith(close, pos)) {
596
+ let j = pos + close.length
597
+ while (j < e) { const d = src.charCodeAt(j); if (d === 32 || d === 9) j++; else break }
598
+ if (j >= e) {
599
+ const value = pos === bodyStart ? '' : src.slice(bodyStart, pos - prevEolLen)
600
+ const node = {type: m.type, value, position: {start: P(1, 0, 0), end: P(line, pos, e)}}
601
+ root.children.push(node)
602
+ lineNo = line; ls = pos; le = e; eolLen = eolL
603
+ lineTok = true; lineCont = true
604
+ setE1(lineNo, ls, le)
605
+ e2Line = lineNo; e2Ls = ls; e2Off = le
606
+ noContainers = false
607
+ return true
608
+ }
609
+ }
610
+ if (eolL === 0) break // content line without EOL at the end of input: not closed
611
+ pos = e + eolL; prevEolLen = eolL; line++
612
+ }
613
+ }
614
+ return false
615
+ }
616
+
617
+ // a Set-like view over a parse's own ids plus ids from earlier (finished) parts of a stream
618
+ function composite(a, b) {
619
+ return {has: id => a.has(id) || b.has(id), get size() { return a.size + b.size }}
620
+ }
621
+
622
+ export function parse(source, opts) {
623
+ doInline = !opts || opts.inline !== false
624
+ mathOn = Boolean(opts && opts.math)
625
+ setMath(mathOn, !opts || !opts.math || opts.math.single !== false)
626
+ streamOn = Boolean(opts && opts.stream)
627
+ if (!(opts && opts.tail)) {
628
+ if (source.charCodeAt(0) === 0xfeff) source = source.slice(1)
629
+ if (source.indexOf('\0') !== -1) source = source.replaceAll('\0', '\uFFFD')
630
+ }
631
+ initDefs = (opts && opts.defs) || null; initFns = (opts && opts.fnDefs) || null
632
+ streamSafe = null; streamEntries = []; defLog.length = 0; fnLog.length = 0; rcLog.length = 0
633
+ src = source; len = source.length
634
+ hasCR = source.indexOf('\r') !== -1
635
+ root = {type: 'root', children: [], position: undefined}
636
+ stk = [cont(ROOT, root)]; sp = 1
637
+ leaf = NONE; pendingBlank = false; pCand = false; pLines.length = 0
638
+ e1Line = 1; e1Ls = 0; e1Off = 0
639
+ e2Line = 1; e2Ls = 0; e2Off = 0; ebLine = 0; ebLs = 0; ebOff = 0; ebBi = 0
640
+ quirkItem = null; lateBlank = false; icPend = ''; cVal = ''; tState = 0; dm = -2
641
+ tNode = null
642
+ pend.length = 0; defIds = new Set(); fnIds = new Set(); pPos.length = 0; emptyItemLine = -1; e12Line = -2; bqRun = 0; lastBqBlank = false
643
+ lineNo = 0
644
+ noContainers = false
645
+ let p = 0
646
+ if (opts && opts.frontmatter) {
647
+ if (opts.frontmatter.some(m => m.anywhere)) throw new Error('samemark: frontmatter `anywhere` is not supported natively')
648
+ if (tryFrontmatter(opts.frontmatter)) p = le + eolLen
649
+ }
650
+ while (p < len) {
651
+ ls = p
652
+ if (!hasCR) {
653
+ le = src.indexOf('\n', p)
654
+ if (le === -1) { le = len; eolLen = 0 } else eolLen = 1
655
+ } else {
656
+ let i = p
657
+ for (;;) {
658
+ if (i >= len) { le = len; eolLen = 0; break }
659
+ const c = src.charCodeAt(i)
660
+ if (c === 10) { le = i; eolLen = 1; break }
661
+ if (c === 13) { le = i; eolLen = src.charCodeAt(i + 1) === 10 ? 2 : 1; break }
662
+ i++
663
+ }
664
+ }
665
+ lineNo++
666
+ processLine()
667
+ p = le + eolLen
668
+ }
669
+ // EOF
670
+ eolLenAtEof = eolLen
671
+ const endsEol = len > 0 && (src.charCodeAt(len - 1) === 10 || src.charCodeAt(len - 1) === 13)
672
+ eolLenAtEof = endsEol ? 1 : 0
673
+ let hasBq = false
674
+ for (let i = 1; i < sp; i++) if (stk[i].kind === BQ) hasBq = true
675
+ eofClose(!hasBq)
676
+ if (eolLen === 0 && bqLastLine) { closeOver = () => P(e1Line, e1Ls, e1Off); closeContainers(1); closeOver = null } else closeContainers(1)
677
+ let endP
678
+ if (len === 0) endP = P(1, 0, 0)
679
+ else if (endsEol) endP = P(lineNo + 1, len, len)
680
+ else endP = P(lineNo, ls, len)
681
+ root.position = {start: P(1, 0, 0), end: endP}
682
+ if (doInline && pend.length) {
683
+ setSource(src)
684
+ setDefinitions(initDefs === null ? defIds : composite(defIds, initDefs), initFns === null ? fnIds : composite(fnIds, initFns))
685
+ setTrackDefs(streamOn)
686
+ for (let i = 0; i < pend.length; i += 8) {
687
+ const node = pend[i], task = pend[i + 1], ml = pend[i + 6]
688
+ const tk = task === 1 || task === 2 || task === 3 || task === 11 || task === 12 ? task : 0
689
+ const startLocal = streamOn ? Object.assign({}, node.position.start) : null
690
+ if (ml !== null) inlineMulti(node, ml, tk)
691
+ else inlineSingle(node, pend[i + 2], pend[i + 3], pend[i + 4], pend[i + 5], task === 'table', tk)
692
+ if (streamOn) {
693
+ const nd = takeNeeds()
694
+ if (nd !== null) streamEntries.push({top: pend[i + 7], node, task: tk, table: task === 'table', line: pend[i + 2], ls: pend[i + 3], cs: pend[i + 4], ce: pend[i + 5], ml, startLocal, neg: nd.neg, pos: nd.pos})
695
+ }
696
+ }
697
+ setTrackDefs(false)
698
+ pend.length = 0
699
+ }
700
+ const r = root
701
+ root = null; src = ''
702
+ return r
703
+ }
704
+
705
+ function eofClose(eolOk) {
706
+ switch (leaf) {
707
+ case NONE: return
708
+ case FCODE: leaf = NONE; finishFCode(false, eolOk && eolLenAtEof > 0, true); return
709
+ case HTML: leaf = NONE; finishHtml(eolOk && eolLenAtEof > 0, true); return
710
+ default: closeLeaf(false)
711
+ }
712
+ }
713
+
714
+ function prep(ci, container, eolInc) {
715
+ // close open leaf, pop unmatched containers; pop LIST if non-item child
716
+ closeLeaf(eolInc)
717
+ if (stk[ci].kind === LIST && container) { /* handled by caller */ }
718
+ closeContainers(ci + 1)
719
+ }
720
+
721
+ let lateBlank = false
722
+ function attribBlank(ci) {
723
+ let i = ci
724
+ while (i > 0 && stk[i].kind === FN) i--
725
+ const c = stk[i]
726
+ if (c.kind === ITEM) c.node.spread = true
727
+ else if (c.kind === LIST) lateBlank = true
728
+ }
729
+
730
+ function processLine() {
731
+ if (!lastBqBlank) bqRun = 0
732
+ lastBqBlank = false
733
+ pos = ls; col = 0; vleft = 0
734
+ lineTok = false; lineCont = false; lastBq = 0
735
+ lateBlank = false
736
+ failKind = -1
737
+ let d = 1
738
+ let bqLine = false
739
+ let fnLine = false, fnTail = false
740
+ for (; d < sp; d++) {
741
+ const c = stk[d]
742
+ const k = c.kind
743
+ if (k === BQ) {
744
+ findNSL(4)
745
+ if (indent <= 3 && ns < le && src.charCodeAt(ns) === 62) {
746
+ advance(indent + 1)
747
+ if (pos < le) { const ch = src.charCodeAt(pos); if (ch === 32 || ch === 9) advance(1) }
748
+ bqLine = true; bqIdx = d; fnTail = false
749
+ continue
750
+ }
751
+ failKind = BQ
752
+ break
753
+ }
754
+ if (k === LIST) continue
755
+ if (k === ITEM && !c.sb && !c.fb) findNSL(c.cc)
756
+ else findNS()
757
+ if (k === ITEM) {
758
+ fnTail = false
759
+ if (blank) { c.fb = c.fb || c.sb; advance(indent < c.cc ? indent : c.cc); continue }
760
+ if (c.fb || indent === 0) { c.fb = false; c.sb = false; failKind = ITEM; break }
761
+ c.fb = false; c.sb = false
762
+ if (indent >= c.cc) { advance(c.cc); continue }
763
+ failKind = ITEM
764
+ break
765
+ }
766
+ // FN
767
+ if (blank) continue
768
+ if (indent >= 4) { advance(4); fnLine = true; fnTail = true; continue }
769
+ // micromark quirk: a nested footnote definition sees the previous footnote indent token as its own
770
+ if (indent === 0 && fnTail) continue
771
+ failKind = FN
772
+ break
773
+ }
774
+ const allMatched = d >= sp
775
+ closeAtPos = bqLine || fnLine ? pos : -1
776
+ matchedPos = pos
777
+ closeFn = fnLine
778
+ bqLastLine = bqLine
779
+ if (bqLine) { lineTok = true; lastBq = bqIdx }
780
+ findNS()
781
+
782
+ if (allMatched) {
783
+ if (leaf === FCODE) { fcodeLine(); finishLine(); return }
784
+ if (leaf === HTML) { if (htmlLine()) { finishLine(); return } }
785
+ else if (leaf === ICODE) {
786
+ if (icClosed) { closeLeaf(false); icClosed = false } // micromark: such code is one line, the next starts a new block
787
+ else if (indent >= 4 || blank) {
788
+ icodeLine(); finishLine(); return
789
+ }
790
+ } else if (leaf === TABLE) {
791
+ if (tState === 1) { tState = 2; lineTok = true; lineCont = true; setE1(lineNo, ls, le); e2Line = lineNo; e2Ls = ls; e2Off = le; tNode.position.end = P(lineNo, ls, le); return }
792
+ }
793
+ }
794
+
795
+ let ci = d - 1
796
+
797
+ if (blank) {
798
+ // blank line
799
+ if (bqLine) { bqRun++; lastBqBlank = true }
800
+ if (leaf !== NONE) closeLeaf(failKind === ITEM)
801
+ if (bqLine && eolLen === 0 && pos === le && vleft === 0 && sp > ci + 1) {
802
+ // last line without EOL: unmatched inner containers close right after the matched prefix
803
+ closeAt(ci + 1, pos)
804
+ } else if (bqLine && eolLen > 0 && sp > ci + 1) {
805
+ const r = bqRun; bqRun = r - 1 // (one line ending less is inside the list when the blank line has its own EOL)
806
+ closeContainers(ci + 1)
807
+ bqRun = r
808
+ } else closeContainers(ci + 1)
809
+ pendingBlank = true; pbCi = ci; pbBq = stk[ci].kind === BQ
810
+ // streaming: a complete blank line at the top level with nothing open is a point after which the past can no longer change
811
+ if (streamOn && sp === 1 && leaf === NONE && eolLen > 0 && !noContainers && !(src.charCodeAt(le) === 13 && le + 1 >= len)) streamSafe = {off: le + eolLen, line: lineNo, n: root.children.length}
812
+ finishLine()
813
+ return
814
+ }
815
+ // a blank line inside a container that has been closed since (e.g. an empty `>` line) is not a blank
816
+ // between the children of an outer item
817
+ if (pendingBlank) { if (!(pbBq && pbCi > ci)) attribBlank(ci); pendingBlank = false }
818
+
819
+ let paraTip = leaf === PARA
820
+ const interrupt = allMatched && (paraTip || (leaf === ICODE && !icClosed))
821
+
822
+ for (;;) {
823
+ findNS()
824
+ if (blank) {
825
+ // rest of line blank after container start
826
+ finishLine(); return
827
+ }
828
+ if (indent >= 4) {
829
+ if (!paraTip) {
830
+ const sp0 = sp
831
+ ci = prepCI(ci, true, false)
832
+ icClosed = sp < sp0
833
+ startICode()
834
+ finishLine(); return
835
+ }
836
+ break
837
+ }
838
+ const ch = src.charCodeAt(ns)
839
+ if (ch === 62 && !noContainers) { // >
840
+ ci = prepCI(ci, true, true, true)
841
+ paraTip = false
842
+ const node = {type: 'blockquote', children: [], position: {start: P(lineNo, ls, ns), end: undefined}}
843
+ stk[ci].node.children.push(node)
844
+ stk[sp++] = cont(BQ, node)
845
+ ci = sp - 1
846
+ advance(indent + 1)
847
+ if (pos < le) { const c2 = src.charCodeAt(pos); if (c2 === 32 || c2 === 9) advance(1) }
848
+ lineTok = true; lastBq = sp - 1
849
+ continue
850
+ }
851
+ if (ch === 35) { // #
852
+ let n = 1
853
+ while (src.charCodeAt(ns + n) === 35) n++
854
+ if (n <= 6) {
855
+ const nx = ns + n
856
+ const c2 = nx < le ? src.charCodeAt(nx) : 10
857
+ if (nx >= le || c2 === 32 || c2 === 9) {
858
+ ci = prepCI(ci, true, false)
859
+ const hnode = {
860
+ type: 'heading', depth: n, children: [],
861
+ position: {start: P(lineNo, ls, ns), end: P(lineNo, ls, le)}
862
+ }
863
+ stk[ci].node.children.push(hnode)
864
+ if (doInline) atxContent(hnode, ns + n)
865
+ lineTok = true; lineCont = true
866
+ finishLine(); return
867
+ }
868
+ }
869
+ break
870
+ }
871
+ if (ch === 96 || ch === 126 || (ch === 36 && mathOn)) {
872
+ if (startFence(ch, ci)) { finishLine(); return }
873
+ break
874
+ }
875
+ if (ch === 60) {
876
+ const k = htmlStart(paraTip && allMatched)
877
+ if (k) {
878
+ if (k === 7 && paraTip && !allMatched && eolLen > 0) {
879
+ // micromark: an html (kind 7) start on a lazy line is allowed and stays in the open containers
880
+ closeLeaf(false)
881
+ ci = sp - 1
882
+ startHtml(k)
883
+ finishLine(); return
884
+ }
885
+ ci = prepCI(ci, true, false)
886
+ startHtml(k)
887
+ finishLine(); return
888
+ }
889
+ break
890
+ }
891
+ if (ch === 61 && paraTip && allMatched && isSetext(ch)) {
892
+ if (paragraphHasContentAfterDefs()) {
893
+ doSetext(1, ci)
894
+ finishLine(); return
895
+ }
896
+ // only definitions so far: this line starts a new paragraph (micromark)
897
+ closeLeaf(false); paraTip = false
898
+ break
899
+ }
900
+ if (ch === 45 || ch === 42 || ch === 95) {
901
+ const thematic = isThematic(ch)
902
+ if (ch !== 95 && !thematic) {
903
+ if (tryItem(ci, ch, interrupt)) { paraTip = false; ci = sp - 1; if (blank2) { finishLine(); return } continue }
904
+ }
905
+ if (ch === 45 && paraTip && allMatched && isSetext(45)) {
906
+ if (paragraphHasContentAfterDefs()) {
907
+ doSetext(2, ci)
908
+ finishLine(); return
909
+ }
910
+ if (!thematic) { closeLeaf(false); paraTip = false; break }
911
+ }
912
+ if (thematic) {
913
+ ci = prepCI(ci, true, false)
914
+ stk[ci].node.children.push({type: 'thematicBreak', position: {start: P(lineNo, ls, ns), end: P(lineNo, ls, le)}})
915
+ lineTok = true; lineCont = true
916
+ finishLine(); return
917
+ }
918
+ break
919
+ }
920
+ if (ch === 43 || (ch >= 48 && ch <= 57)) {
921
+ if (tryItem(ci, ch, interrupt)) { paraTip = false; ci = sp - 1; if (blank2) { finishLine(); return } continue }
922
+ break
923
+ }
924
+ if (ch === 91 && src.charCodeAt(ns + 1) === 94) {
925
+ if (tryFootnote(ci)) { paraTip = false; ci = sp - 1; continue }
926
+ break
927
+ }
928
+ break
929
+ }
930
+ // paragraph / table / continuation
931
+ // table head?
932
+ if (leaf === TABLE && allMatched) {
933
+ addTableRow()
934
+ finishLine(); return
935
+ }
936
+ if (allMatched || !paraTip || true) {
937
+ if (!paraTip || (allMatched && leaf === PARA) || !allMatched) {
938
+ // table head detection
939
+ if ((allMatched || !paraTip) && tryTableHead(ci)) { finishLine(); return }
940
+ // micromark: a lazy line may be the head row of a table when the delimiter row line matches all containers
941
+ if (!allMatched && paraTip && tryTableHead(sp - 1)) { finishLine(); return }
942
+ }
943
+ }
944
+ if (paraTip) {
945
+ // continuation (lazy or not)
946
+ findNS()
947
+ paraLine(false)
948
+ finishLine(); return
949
+ }
950
+ // new paragraph
951
+ ci = prepCI(ci, true, false)
952
+ findNS()
953
+ startPara()
954
+ finishLine()
955
+ }
956
+
957
+ let blank2 = false
958
+
959
+ // ATX heading content range: port of micromark resolveHeadingAtx over the token list
960
+ let atxT = [], atxS = [], atxE = []
961
+ function atxContent(node, from) {
962
+ // token types: 0 sequence, 1 whitespace, 2 text
963
+ let nt = 0
964
+ atxT[0] = 0; atxS[0] = ns; atxE[0] = from
965
+ nt = 1
966
+ let i = from
967
+ while (i < le) {
968
+ const c = src.charCodeAt(i)
969
+ let ty
970
+ const st = i
971
+ if (c === 35) { while (i < le && src.charCodeAt(i) === 35) i++; ty = 0 }
972
+ else if (c === 32 || c === 9) { while (i < le && (src.charCodeAt(i) === 32 || src.charCodeAt(i) === 9)) i++; ty = 1 }
973
+ else { while (i < le) { const d = src.charCodeAt(i); if (d === 35 || d === 32 || d === 9) break; i++ } ty = 2 }
974
+ atxT[nt] = ty; atxS[nt] = st; atxE[nt] = i; nt++
975
+ }
976
+ // events: 0 enter heading, enter token j = 1 + 2j, exit token j = 2 + 2j, last = exit heading
977
+ const evLen = 2 + 2 * nt
978
+ const typeAt = idx => (idx <= 0 || idx >= evLen - 1) ? -1 : atxT[(idx - 1) >> 1]
979
+ let contentEnd = evLen - 2
980
+ let contentStart = 3
981
+ if (typeAt(contentStart) === 1) contentStart += 2
982
+ if (contentEnd - 2 > contentStart && typeAt(contentEnd) === 1) contentEnd -= 2
983
+ if (typeAt(contentEnd) === 0 && (contentStart === contentEnd - 1 || (contentEnd - 4 > contentStart && typeAt(contentEnd - 2) === 1))) {
984
+ contentEnd -= contentStart + 1 === contentEnd ? 2 : 4
985
+ }
986
+ if (contentEnd > contentStart) {
987
+ const a = atxS[(contentStart - 1) >> 1], b = atxE[(contentEnd - 1) >> 1]
988
+ pend.push(node, false, lineNo, ls, a, b, null, topNow())
989
+ }
990
+ }
991
+
992
+ // micromark: when unmatched containers are closed by a NEW container starting on the same line and a
993
+ // blockquote prefix was consumed on that line, their exit sits right after that prefix (the `_container`
994
+ // fix cannot swap it before the previous line ending)
995
+ let closeAtPos = -1, closeFn = false, closeEol12 = false, matchedPos = 0
996
+ // last line on which an unclosed fence / html block included its EOL (its container exits sit after the next line's prefix)
997
+ let e12Line = -2
998
+ // micromark: a list that ends in 2+ blockquote-prefix-only lines is spread (those line endings stay inside it)
999
+ let bqRun = 0, lastBqBlank = false
1000
+ let bqLastLine = false
1001
+ let closeOver = null
1002
+ function closeAt(to, at) {
1003
+ const a = e1Line, b = e1Ls, c = e1Off
1004
+ setE1(lineNo, ls, at)
1005
+ closeOver = () => P(lineNo, ls, at)
1006
+ closeContainers(to)
1007
+ closeOver = null
1008
+ setE1(a, b, c)
1009
+ }
1010
+ function closeForNew(to) {
1011
+ closeEol12 = false
1012
+ let at = closeAtPos
1013
+ if (at < 0 && e12Line === lineNo) { at = matchedPos; closeEol12 = true }
1014
+ if (at >= 0 && sp > to) {
1015
+ if (!closeEol12) bqRun++ // the exit sits after this line's prefix: one more trailing line ending counts
1016
+ closeAt(to, at)
1017
+ if (!closeEol12) bqRun--
1018
+ } else closeContainers(to)
1019
+ }
1020
+ function prepCI(ci, isBlock, eolInc, newContainer) {
1021
+ closeLeaf(eolInc)
1022
+ if (ci < sp - 1 && true) {
1023
+ // pop unmatched
1024
+ if (newContainer) closeForNew(ci + 1); else closeContainers(ci + 1)
1025
+ }
1026
+ if (stk[ci].kind === LIST) {
1027
+ if (newContainer) closeForNew(ci); else closeContainers(ci)
1028
+ ci--
1029
+ if (lateBlank) { lateBlank = false; attribBlank(ci) }
1030
+ }
1031
+ return ci
1032
+ }
1033
+
1034
+ function finishLine() {
1035
+ if (!lastBqBlank) bqRun = 0
1036
+ if (lineTok) {
1037
+ setE1(lineNo, ls, le)
1038
+ if (lineCont) { e2Line = lineNo; e2Ls = ls; e2Off = le }
1039
+ else { ebLine = lineNo; ebLs = ls; ebOff = le; ebBi = lastBq }
1040
+ }
1041
+ }
1042
+ // end point for an item at stack index k closed by a following sibling item
1043
+ function pickE2(k) {
1044
+ if (ebLine > (e2Off === e2Ls ? e2Line - 1 : e2Line) && ebBi > k) return P(ebLine, ebLs, ebOff)
1045
+ return P(e2Line, e2Ls, e2Off)
1046
+ }
1047
+
1048
+ function startPara() {
1049
+ leaf = PARA
1050
+ pL = lineNo; pLs = ls; pOff = ns
1051
+ eL = lineNo; eLs = ls; eOff = le
1052
+ pCand = src.charCodeAt(ns) === 91
1053
+ pLines.length = 0; pPos.length = 0; pVl.length = 0
1054
+ pLines.push(lineNo, ls, ns, le); pPos.push(pos); pVl.push(vleft)
1055
+ lineTok = true; lineCont = true
1056
+ }
1057
+
1058
+ function paraLine() {
1059
+ eL = lineNo; eLs = ls; eOff = le
1060
+ pLines.push(lineNo, ls, ns, le); pPos.push(pos); pVl.push(vleft)
1061
+ lineTok = true; lineCont = true
1062
+ }
1063
+
1064
+ function startICode() {
1065
+ leaf = ICODE
1066
+ cL = lineNo; cLs = ls; cOff = pos
1067
+ advance(4)
1068
+ cVal = restOfLine()
1069
+ cLastL = lineNo; cLastLs = ls; cLastLe = le
1070
+ cEol = eolStr(le, eolLen)
1071
+ icPend = ''
1072
+ lineTok = true; lineCont = true
1073
+ }
1074
+
1075
+ function icodeLine() {
1076
+ // continuing indented code (allMatched) with indent>=4 or blank
1077
+ if (blank) {
1078
+ const n = indent
1079
+ advance(n > 4 ? 4 : n)
1080
+ if (n >= 4) {
1081
+ cVal += icPend + cEol + restOfLine()
1082
+ icPend = ''
1083
+ cEol = eolStr(le, eolLen)
1084
+ cLastL = lineNo; cLastLs = ls; cLastLe = le
1085
+ lineTok = true; lineCont = true
1086
+ pendingBlank = false
1087
+ return
1088
+ }
1089
+ icPend += cEol + restOfLine()
1090
+ cEol = eolStr(le, eolLen)
1091
+ pendingBlank = true; pbBq = false
1092
+ return
1093
+ }
1094
+ advance(4)
1095
+ cVal += icPend + cEol + restOfLine()
1096
+ icPend = ''
1097
+ cEol = eolStr(le, eolLen)
1098
+ cLastL = lineNo; cLastLs = ls; cLastLe = le
1099
+ pendingBlank = false
1100
+ lineTok = true; lineCont = true
1101
+ }
1102
+
1103
+ // ---- fenced code ----
1104
+ function startFence(ch, ci) {
1105
+ let n = 1
1106
+ while (src.charCodeAt(ns + n) === ch) n++
1107
+ if (n < (ch === 36 ? 2 : 3)) return false
1108
+ const m = ns + n
1109
+ if (ch === 36) {
1110
+ // math flow: the rest of the line is the meta (raw, may not contain `$`)
1111
+ let k = m
1112
+ while (k < le && isWs(src.charCodeAt(k))) k++
1113
+ for (let q = k; q < le; q++) if (src.charCodeAt(q) === 36) return false
1114
+ let mm = k < le ? src.slice(k, le) : null
1115
+ if (mm !== null && (mm.indexOf('\\') !== -1 || mm.indexOf('&') !== -1)) mm = decodeString(mm)
1116
+ ci = prepCI(ci, true, false)
1117
+ leaf = FCODE
1118
+ cL = lineNo; cLs = ls; cOff = ns
1119
+ fch = ch; flen = n; findent = indent
1120
+ flang = null; fmeta = mm
1121
+ fEolCR = src.charCodeAt(le) === 13 && src.charCodeAt(le + 1) !== 10
1122
+ cVal = ''; cN = 0
1123
+ cLastL = lineNo; cLastLs = ls; cLastLe = le
1124
+ cRealL = lineNo; cRealLs = ls; cRealLe = le
1125
+ lineTok = true; lineCont = true
1126
+ return true
1127
+ }
1128
+ if (ch === 96) {
1129
+ for (let i = m; i < le; i++) if (src.charCodeAt(i) === 96) return false
1130
+ }
1131
+ // info
1132
+ let i = m
1133
+ while (i < le && isWs(src.charCodeAt(i))) i++
1134
+ let lang = null, meta = null
1135
+ if (i < le) {
1136
+ let j = i
1137
+ while (j < le && !isWs(src.charCodeAt(j))) j++
1138
+ lang = src.slice(i, j)
1139
+ let k = j
1140
+ while (k < le && isWs(src.charCodeAt(k))) k++
1141
+ if (k < le) {
1142
+ meta = src.slice(k, le)
1143
+ }
1144
+ }
1145
+ if (lang !== null && (lang.indexOf('\\') !== -1 || lang.indexOf('&') !== -1)) lang = decodeString(lang)
1146
+ if (meta !== null && (meta.indexOf('\\') !== -1 || meta.indexOf('&') !== -1)) meta = decodeString(meta)
1147
+ ci = prepCI(ci, true, false)
1148
+ leaf = FCODE
1149
+ cL = lineNo; cLs = ls; cOff = ns
1150
+ fch = ch; flen = n; findent = indent
1151
+ flang = lang; fmeta = meta
1152
+ fEolCR = src.charCodeAt(le) === 13 && src.charCodeAt(le + 1) !== 10
1153
+ cVal = ''; cN = 0
1154
+ cLastL = lineNo; cLastLs = ls; cLastLe = le
1155
+ cRealL = lineNo; cRealLs = ls; cRealLe = le
1156
+ lineTok = true; lineCont = true
1157
+ return true
1158
+ }
1159
+
1160
+ function fcodeLine() {
1161
+ // allMatched, in fenced code
1162
+ if (indent <= 3 && !blank) {
1163
+ if (src.charCodeAt(ns) === fch) {
1164
+ let n = 1
1165
+ while (src.charCodeAt(ns + n) === fch) n++
1166
+ if (n >= flen) {
1167
+ let i = ns + n
1168
+ while (i < le && isWs(src.charCodeAt(i))) i++
1169
+ if (i >= le) {
1170
+ cLastL = lineNo; cLastLs = ls; cLastLe = le
1171
+ leaf = NONE
1172
+ finishFCode(true, false)
1173
+ lineTok = true; lineCont = true
1174
+ return
1175
+ }
1176
+ }
1177
+ }
1178
+ }
1179
+ const n = indent < findent ? indent : findent
1180
+ advance(n)
1181
+ const piece = restOfLine()
1182
+ if (cN === 0) cVal = piece
1183
+ else cVal += cEol + piece
1184
+ cN++
1185
+ cEol = eolStr(le, eolLen)
1186
+ cLastL = lineNo; cLastLs = ls; cLastLe = le
1187
+ // a last line (no EOL) that the open containers consume entirely is not part of the code
1188
+ cRealL = lineNo; cRealLs = ls; cRealLe = le
1189
+ if (blank && eolLen === 0 && piece === '' && n === 0 && sp > 1) cLastLe = ls
1190
+ if (!blank) lineTok = true; lineCont = true
1191
+ }
1192
+
1193
+ // ---- html ----
1194
+ const RAW = ['script', 'pre', 'style', 'textarea']
1195
+ function lc(c) { return c | 32 }
1196
+ function isAlpha(c) { c |= 32; return c >= 97 && c <= 122 }
1197
+ function isAlnum(c) { return isAlpha(c) || (c >= 48 && c <= 57) }
1198
+
1199
+ function htmlStart(paraTip) {
1200
+ const c1 = src.charCodeAt(ns + 1)
1201
+ if (c1 === 33) {
1202
+ const c2 = src.charCodeAt(ns + 2)
1203
+ if (c2 === 45) return src.charCodeAt(ns + 3) === 45 ? 2 : 0
1204
+ if (c2 === 91) return src.startsWith('CDATA[', ns + 3) ? 5 : 0
1205
+ if (isAlpha(c2)) return 4
1206
+ return 0
1207
+ }
1208
+ if (c1 === 63) return 3
1209
+ let i = ns + 1
1210
+ let closing = false
1211
+ if (c1 === 47) { closing = true; i++ }
1212
+ const c = src.charCodeAt(i)
1213
+ if (!isAlpha(c)) return 0
1214
+ const s = i
1215
+ while (i < le && (isAlnum(src.charCodeAt(i)) || src.charCodeAt(i) === 45)) i++
1216
+ const name = src.slice(s, i).toLowerCase()
1217
+ const after = i < le ? src.charCodeAt(i) : 10
1218
+ const terminal = i >= le || after === 62 || after === 47 || after === 32 || after === 9
1219
+ if (terminal) {
1220
+ if (after !== 47 && !closing && RAW.indexOf(name) !== -1) return 1
1221
+ if (BLOCK_NAMES.has(name)) {
1222
+ if (after === 47) return src.charCodeAt(i + 1) === 62 ? 6 : 0
1223
+ return 6
1224
+ }
1225
+ }
1226
+ if (paraTip) return 0
1227
+ if (!terminal) return 0
1228
+ // kind 7: complete tag
1229
+ const e = completeTag(i, closing)
1230
+ if (e < 0) return 0
1231
+ let j = e
1232
+ while (j < le && isWs(src.charCodeAt(j))) j++
1233
+ return j >= le ? 7 : 0
1234
+ }
1235
+
1236
+ // i: index after tag name; returns index after '>' or -1
1237
+ function completeTag(i, closing) {
1238
+ if (closing) {
1239
+ while (i < le && isWs(src.charCodeAt(i))) i++
1240
+ return src.charCodeAt(i) === 62 && i < le ? i + 1 : -1
1241
+ }
1242
+ // port of micromark html-flow complete* states (kind 7); the end of the line behaves like an EOL code
1243
+ let st = 0, marker = 0
1244
+ for (;;) {
1245
+ const c = i < le ? src.charCodeAt(i) : -1
1246
+ switch (st) {
1247
+ case 0: // attribute name before
1248
+ if (c === 47) { i++; st = 7; continue }
1249
+ if (c === 58 || c === 95 || isAlpha(c)) { i++; st = 1; continue }
1250
+ if (c === 32 || c === 9) { i++; continue }
1251
+ st = 7
1252
+ continue
1253
+ case 1: // attribute name
1254
+ if (c === 45 || c === 46 || c === 58 || c === 95 || isAlnum(c)) { i++; continue }
1255
+ st = 2
1256
+ continue
1257
+ case 2: // attribute name after
1258
+ if (c === 61) { i++; st = 3; continue }
1259
+ if (c === 32 || c === 9) { i++; continue }
1260
+ st = 0
1261
+ continue
1262
+ case 3: // attribute value before
1263
+ if (c === -1 || c === 60 || c === 61 || c === 62 || c === 96) return -1
1264
+ if (c === 34 || c === 39) { i++; marker = c; st = 4; continue }
1265
+ if (c === 32 || c === 9) { i++; continue }
1266
+ st = 5
1267
+ continue
1268
+ case 4: // quoted
1269
+ if (c === marker) { i++; st = 6; continue }
1270
+ if (c === -1) return -1
1271
+ i++
1272
+ continue
1273
+ case 5: // unquoted
1274
+ if (c === -1 || c === 34 || c === 39 || c === 47 || c === 60 || c === 61 || c === 62 || c === 96 || c === 32 || c === 9) { st = 2; continue }
1275
+ i++
1276
+ continue
1277
+ case 6: // quoted after
1278
+ if (c === 47 || c === 62 || c === 32 || c === 9) { st = 0; continue }
1279
+ return -1
1280
+ case 7: // end
1281
+ return c === 62 ? i + 1 : -1
1282
+ }
1283
+ }
1284
+ }
1285
+
1286
+ // index of str within the current line [from, le), else -1 (never scans past the line)
1287
+ function idxLine(str, from) {
1288
+ const c0 = str.charCodeAt(0), n = str.length
1289
+ const end = le - n
1290
+ for (let i = from; i <= end; i++) {
1291
+ if (src.charCodeAt(i) === c0 && (n === 1 || src.startsWith(str, i))) return i
1292
+ }
1293
+ return -1
1294
+ }
1295
+
1296
+ function htmlEnds(k, from) {
1297
+ // does the line (from index) contain the end condition for kind k
1298
+ switch (k) {
1299
+ case 1: {
1300
+ let i = from
1301
+ for (;;) {
1302
+ i = idxLine('</', i)
1303
+ if (i === -1) return false
1304
+ const s = src.slice(i + 2, i + 12).toLowerCase()
1305
+ if (s.startsWith('script>') || s.startsWith('style>') || s.startsWith('pre>') || s.startsWith('textarea>')) return true
1306
+ i += 2
1307
+ }
1308
+ }
1309
+ case 2: return idxLine('-->', from) !== -1
1310
+ case 3: return idxLine('?>', from) !== -1
1311
+ case 4: return idxLine('>', from) !== -1
1312
+ case 5: {
1313
+ // micromark's state machine: `]]]>` does not end the block, `]]]]>` does
1314
+ let st = 0
1315
+ for (let i = from; i < le; i++) {
1316
+ const c = src.charCodeAt(i)
1317
+ if (st === 0) st = c === 93 ? 1 : 0
1318
+ else if (st === 1) st = c === 93 ? 2 : 0
1319
+ else { if (c === 62) return true; st = c === 93 ? 1 : 0 }
1320
+ }
1321
+ return false
1322
+ }
1323
+ }
1324
+ return false
1325
+ }
1326
+
1327
+ function startHtml(k) {
1328
+ cRealL = lineNo; cRealLs = ls; cRealLe = le
1329
+ leaf = HTML
1330
+ hkind = k
1331
+ cL = lineNo; cLs = ls; cOff = pos
1332
+ cVal = restOfLine()
1333
+ cLastL = lineNo; cLastLs = ls; cLastLe = le
1334
+ lineTok = true; lineCont = true
1335
+ if (k <= 5) {
1336
+ const from = k === 2 ? ns + 2 : k === 3 ? ns + 1 : k === 5 ? ns + 9 : ns + 1
1337
+ if (htmlEnds(k, from)) { leaf = NONE; finishHtml(false) }
1338
+ }
1339
+ }
1340
+
1341
+ // returns true if consumed
1342
+ function htmlLine() {
1343
+ if (hkind >= 6) {
1344
+ if (blank) { return false } // caller falls through; need to close
1345
+ advance(indent < 0 ? 0 : 0)
1346
+ cVal += eolStr(cLastLe, 0) + ''
1347
+ }
1348
+ // append line
1349
+ const prevEol = cEolHtml()
1350
+ const rest = restOfLine()
1351
+ cVal += prevEol + rest
1352
+ cLastL = lineNo; cLastLs = ls; cLastLe = le
1353
+ cRealL = lineNo; cRealLs = ls; cRealLe = le
1354
+ if (hkind <= 5 && blank && eolLen === 0 && rest === '' && sp > 1) cLastLe = ls
1355
+ lineTok = true; lineCont = true
1356
+ if (hkind <= 5 && htmlEnds(hkind, pos)) { leaf = NONE; finishHtml(false) }
1357
+ return true
1358
+ }
1359
+ function cEolHtml() {
1360
+ // eol of previous line: src at cLastLe
1361
+ const c = src.charCodeAt(cLastLe)
1362
+ if (c === 13) return src.charCodeAt(cLastLe + 1) === 10 ? '\r\n' : '\r'
1363
+ return '\n'
1364
+ }
1365
+
1366
+ // ---- thematic / setext ----
1367
+ function isThematic(ch) {
1368
+ let n = 0
1369
+ for (let i = ns; i < le; i++) {
1370
+ const c = src.charCodeAt(i)
1371
+ if (c === ch) n++
1372
+ else if (c !== 32 && c !== 9) return false
1373
+ }
1374
+ return n >= 3
1375
+ }
1376
+
1377
+ function isSetext(ch) {
1378
+ let i = ns
1379
+ while (i < le && src.charCodeAt(i) === ch) i++
1380
+ if (i === ns) return false
1381
+ while (i < le && isWs(src.charCodeAt(i))) i++
1382
+ return i >= le
1383
+ }
1384
+
1385
+ function doSetext(depth, ci) {
1386
+ // finalize paragraph into heading
1387
+ leaf = NONE
1388
+ const end = P(lineNo, ls, le)
1389
+ finishParagraph(-1, {depth, end})
1390
+ lineTok = true; lineCont = true
1391
+ }
1392
+
1393
+ // ---- lists ----
1394
+ let quirkItem = null
1395
+ function tryItem(ci, ch, interrupt) {
1396
+ if (noContainers) return false
1397
+ blank2 = false
1398
+ let mlen = 1
1399
+ let ordered = false
1400
+ let delim = ch
1401
+ let value = 0
1402
+ if (ch >= 48 && ch <= 57) {
1403
+ ordered = true
1404
+ let n = 0
1405
+ let i = ns
1406
+ while (i < le) {
1407
+ const c = src.charCodeAt(i)
1408
+ if (c >= 48 && c <= 57) { n++; i++ } else break
1409
+ }
1410
+ if (n > 9) return false
1411
+ delim = src.charCodeAt(i)
1412
+ if (delim !== 46 && delim !== 41) return false
1413
+ if (interrupt && !(n === 1 && ch === 49)) return false
1414
+ mlen = n + 1
1415
+ value = parseInt(src.slice(ns, ns + n), 10)
1416
+ }
1417
+ const m = ns + mlen
1418
+ // after marker
1419
+ let restBlank = true
1420
+ let wsCols = 0
1421
+ let j = m
1422
+ let cc = col + indent + mlen // virtual column after marker, relative to line start
1423
+ // compute whitespace columns following marker
1424
+ {
1425
+ let cur = cc
1426
+ while (j < le) {
1427
+ const c = src.charCodeAt(j)
1428
+ if (c === 32) { cur++; j++ } else if (c === 9) { cur += 4 - (cur & 3); j++ } else break
1429
+ }
1430
+ wsCols = cur - cc
1431
+ restBlank = j >= le
1432
+ }
1433
+ if (restBlank) {
1434
+ if (interrupt) return false
1435
+ } else if (wsCols === 0) return false
1436
+ // sibling / new list
1437
+ const top = stk[ci]
1438
+ let compatible = false
1439
+ if (top.kind === LIST) {
1440
+ compatible = top.ord === ordered && top.mk === delim
1441
+ }
1442
+ // commit
1443
+ const quirk = leaf === FCODE || (leaf === HTML && hkind <= 5)
1444
+ closeLeaf(failKind === ITEM || true)
1445
+ if (top.kind === LIST && compatible) {
1446
+ // sibling
1447
+ const itemNode = stk[ci + 1].node
1448
+ const pe = pickE2(ci + 1)
1449
+ if (e12Line === lineNo) closeForNew(ci + 1); else closeContainers(ci + 1)
1450
+ itemNode.position.end = pe
1451
+ const fnQuirk = closeFn && closeAtPos >= 0 // footnote indent: the item ends at the end of the next item's prefix
1452
+ quirkItem = quirk || fnQuirk ? itemNode : null
1453
+ lateBlank = false
1454
+ if (!quirk && !fnQuirk && pe.line + 1 < lineNo) top.node.spread = true
1455
+ else if (fnQuirk && !quirk && pe.line + 1 < lineNo) itemNode.spread = true
1456
+ } else {
1457
+ closeForNew(ci + 1)
1458
+ if (top.kind === LIST) { closeForNew(ci); ci--; if (lateBlank) { lateBlank = false; attribBlank(ci) } }
1459
+ const lnode = {type: 'list', ordered, start: ordered ? value : null, spread: false, children: [], position: {start: P(lineNo, ls, ns), end: undefined}}
1460
+ stk[ci].node.children.push(lnode)
1461
+ const lc = cont(LIST, lnode)
1462
+ lc.ord = ordered; lc.mk = delim
1463
+ stk[sp++] = lc
1464
+ ci = sp - 1
1465
+ }
1466
+ const lcont = stk[ci]
1467
+ const item = {type: 'listItem', spread: false, checked: null, children: [], position: {start: P(lineNo, ls, ns), end: undefined}}
1468
+ lcont.node.children.push(item)
1469
+ const ic = cont(ITEM, item)
1470
+ const initialSize = indent
1471
+ stk[sp++] = ic
1472
+ advance(indent + mlen)
1473
+ lineTok = true; lineCont = true
1474
+ if (restBlank) {
1475
+ if (quirkItem) { quirkItem.position.end = P(lineNo, ls, pos); quirkItem = null }
1476
+ if (j === m && eolLen > 0) emptyItemLine = lineNo
1477
+ ic.sb = true
1478
+ ic.cc = initialSize + mlen + 1
1479
+ blank2 = true
1480
+ return true
1481
+ }
1482
+ if (wsCols <= 4 && true) {
1483
+ advance(wsCols)
1484
+ ic.cc = initialSize + mlen + wsCols
1485
+ } else {
1486
+ advance(1)
1487
+ ic.cc = initialSize + mlen + 1
1488
+ }
1489
+ if (quirkItem) { quirkItem.position.end = P(lineNo, ls, pos); quirkItem = null }
1490
+ return true
1491
+ }
1492
+
1493
+ // ---- footnote definitions ----
1494
+ function tryFootnote(ci) {
1495
+ if (noContainers) return false
1496
+ let i = ns + 2
1497
+ let size = 0
1498
+ let data = false
1499
+ for (;;) {
1500
+ if (i >= le) return false
1501
+ const c = src.charCodeAt(i)
1502
+ if (size > 999 || (c === 93 && !data) || c === 91 || c === 32 || c === 9) return false
1503
+ if (c === 93) break
1504
+ data = true
1505
+ size++
1506
+ i++
1507
+ if (c === 92) {
1508
+ const d = src.charCodeAt(i)
1509
+ if (d === 91 || d === 92 || d === 93) { i++; size++ }
1510
+ }
1511
+ }
1512
+ if (src.charCodeAt(i + 1) !== 58) return false
1513
+ const raw = src.slice(ns + 2, i)
1514
+ fnIds.add(normalizeIdentifier(raw))
1515
+ ci = prepCI(ci, true, true, true)
1516
+ const label = raw.indexOf('\\') === -1 && raw.indexOf('&') === -1 ? raw : decodeString(raw)
1517
+ const node = {
1518
+ type: 'footnoteDefinition', identifier: normalizeIdentifier(raw).toLowerCase(), label, children: [],
1519
+ position: {start: P(lineNo, ls, ns), end: undefined}
1520
+ }
1521
+ stk[ci].node.children.push(node)
1522
+ if (streamOn) fnLog.push(normalizeIdentifier(raw), topNow())
1523
+ stk[sp++] = cont(FN, node)
1524
+ advance(indent + (i + 2 - ns))
1525
+ // whitespace
1526
+ let w = 0
1527
+ {
1528
+ let cur = col + vleft
1529
+ let j = pos
1530
+ while (j < le) {
1531
+ const c = src.charCodeAt(j)
1532
+ if (c === 32) { cur++; j++ } else if (c === 9) { cur += 4 - (cur & 3); j++ } else break
1533
+ }
1534
+ w = cur - (col + vleft)
1535
+ }
1536
+ advance(w)
1537
+ lineTok = true; lineCont = true
1538
+ return true
1539
+ }
1540
+
1541
+ // ---- tables ----
1542
+ function tryTableHead(ci) {
1543
+ // head row = current line from ns. next line must be delimiter row
1544
+ if (indent >= 4) return false // a paragraph continuation, not a table head
1545
+ if (le + eolLen >= len || eolLen === 0) return false
1546
+ // quick pre-check of next raw line
1547
+ const nls = le + eolLen
1548
+ {
1549
+ let i = nls
1550
+ let dash = false
1551
+ for (;;) {
1552
+ if (i >= len) break
1553
+ const c = src.charCodeAt(i)
1554
+ if (c === 10 || c === 13) break
1555
+ if (c === 45) dash = true
1556
+ else if (c !== 124 && c !== 58 && c !== 32 && c !== 9 && c !== 62) return false
1557
+ i++
1558
+ }
1559
+ if (!dash) return false
1560
+ }
1561
+ const size = headRowSize(ns, le)
1562
+ if (size === 0) return false
1563
+ // peek delimiter row
1564
+ const al = peekDelimiter(size, ci)
1565
+ if (!al) return false
1566
+ // commit table
1567
+ ci = prepCI(ci, true, false)
1568
+ const node = {type: 'table', align: al, children: [], position: {start: P(lineNo, ls, ns), end: undefined}}
1569
+ stk[ci].node.children.push(node)
1570
+ tNode = node
1571
+ leaf = TABLE
1572
+ tState = 1
1573
+ addRow(ns, le)
1574
+ lineTok = true; lineCont = true
1575
+ return true
1576
+ }
1577
+
1578
+ function headRowSize(from, to) {
1579
+ let size = 0, sizeB = 0, seen = false
1580
+ let i = from
1581
+ if (src.charCodeAt(i) !== 124) { seen = true; sizeB += 1 }
1582
+ for (;;) {
1583
+ if (i >= to) return sizeB > 1 ? size : 0
1584
+ const c = src.charCodeAt(i)
1585
+ if (c === 32 || c === 9) { i++; continue }
1586
+ sizeB += 1
1587
+ if (seen) { seen = false; size += 1 }
1588
+ if (c === 124) { seen = true; i++; continue }
1589
+ // data
1590
+ for (;;) {
1591
+ if (i >= to) break
1592
+ const d = src.charCodeAt(i)
1593
+ if (d === 124 || d === 32 || d === 9) break
1594
+ i++
1595
+ if (d === 92) {
1596
+ const e = src.charCodeAt(i)
1597
+ if ((e === 92 || e === 124) && i < to) i++
1598
+ }
1599
+ }
1600
+ }
1601
+ }
1602
+
1603
+ function peekDelimiter(size, ci) {
1604
+ // save state
1605
+ const s_ls = ls, s_le = le, s_eol = eolLen, s_line = lineNo, s_pos = pos, s_col = col, s_vleft = vleft
1606
+ const s_ns = ns, s_indent = indent, s_blank = blank
1607
+ let result = null
1608
+ ls = le + eolLen
1609
+ if (!hasCR) {
1610
+ le = src.indexOf('\n', ls)
1611
+ if (le === -1) { le = len; eolLen = 0 } else eolLen = 1
1612
+ } else {
1613
+ let i = ls
1614
+ for (;;) {
1615
+ if (i >= len) { le = len; eolLen = 0; break }
1616
+ const c = src.charCodeAt(i)
1617
+ if (c === 10) { le = i; eolLen = 1; break }
1618
+ if (c === 13) { le = i; eolLen = src.charCodeAt(i + 1) === 10 ? 2 : 1; break }
1619
+ i++
1620
+ }
1621
+ }
1622
+ lineNo++
1623
+ pos = ls; col = 0; vleft = 0
1624
+ let ok = true
1625
+ const cnt = stk[ci].kind === LIST ? ci : ci + 1
1626
+ for (let d = 1; d < cnt; d++) {
1627
+ const c = stk[d]
1628
+ const k = c.kind
1629
+ if (k === BQ) {
1630
+ findNS()
1631
+ if (indent <= 3 && ns < le && src.charCodeAt(ns) === 62) {
1632
+ advance(indent + 1)
1633
+ if (pos < le) { const ch = src.charCodeAt(pos); if (ch === 32 || ch === 9) advance(1) }
1634
+ continue
1635
+ }
1636
+ ok = false; break
1637
+ }
1638
+ if (k === LIST) continue
1639
+ findNS()
1640
+ if (k === ITEM) {
1641
+ if (blank || c.fb || c.sb || indent === 0 || indent < c.cc) { ok = false; break }
1642
+ advance(c.cc)
1643
+ continue
1644
+ }
1645
+ if (blank || indent < 4) { ok = false; break }
1646
+ advance(4)
1647
+ }
1648
+ if (ok) {
1649
+ findNS()
1650
+ if (!blank) {
1651
+ // not a new container
1652
+ const ch = src.charCodeAt(ns)
1653
+ let container = false
1654
+ if (indent <= 3 && !noContainers) {
1655
+ if (ch === 62) container = true
1656
+ else if ((ch === 45 || ch === 43 || ch === 42) && (ns + 1 >= le || isWs(src.charCodeAt(ns + 1)))) container = true
1657
+ else if (ch >= 48 && ch <= 57) container = true // approximate
1658
+ else if (ch === 91 && src.charCodeAt(ns + 1) === 94) container = true
1659
+ }
1660
+ if (!container || (ch >= 48 && ch <= 57 && !digitItem())) {
1661
+ result = delimRow(ns, le, size, indent)
1662
+ }
1663
+ }
1664
+ }
1665
+ ls = s_ls; le = s_le; eolLen = s_eol; lineNo = s_line; pos = s_pos; col = s_col; vleft = s_vleft
1666
+ ns = s_ns; indent = s_indent; blank = s_blank
1667
+ return result
1668
+ }
1669
+
1670
+ function digitItem() {
1671
+ let i = ns, n = 0
1672
+ while (i < le) { const c = src.charCodeAt(i); if (c >= 48 && c <= 57) { n++; i++ } else break }
1673
+ if (n === 0 || n > 9) return false
1674
+ const d = src.charCodeAt(i)
1675
+ return d === 46 || d === 41
1676
+ }
1677
+
1678
+ // returns align array or null
1679
+ function delimRow(from, to, size, ind) {
1680
+ let i = from
1681
+ let sizeB = 0
1682
+ let seen = false
1683
+ const aligns = []
1684
+ if (ind > 3) return null
1685
+ const c0 = src.charCodeAt(i)
1686
+ if (c0 === 124) { seen = true; i++ }
1687
+ else if (c0 !== 45 && c0 !== 58) return null
1688
+ for (;;) {
1689
+ // cell: ws, :?-+:? ws
1690
+ while (i < to && isWs(src.charCodeAt(i))) i++
1691
+ let c = src.charCodeAt(i)
1692
+ if (i >= to) break
1693
+ let left = false, right = false
1694
+ if (c === 58) { sizeB++; seen = true; left = true; i++; c = src.charCodeAt(i); if (c !== 45) return null }
1695
+ else if (c === 45) { sizeB++ }
1696
+ else return null
1697
+ while (src.charCodeAt(i) === 45 && i < to) i++
1698
+ if (src.charCodeAt(i) === 58 && i < to) { seen = true; right = true; i++ }
1699
+ while (i < to && isWs(src.charCodeAt(i))) i++
1700
+ aligns.push(left && right ? 'center' : left ? 'left' : right ? 'right' : null)
1701
+ if (i >= to) break
1702
+ if (src.charCodeAt(i) === 124) {
1703
+ seen = true; i++
1704
+ continue
1705
+ }
1706
+ return null
1707
+ }
1708
+ if (!seen || size !== sizeB) return null
1709
+ return aligns
1710
+ }
1711
+
1712
+ function addTableRow() {
1713
+ // body row: line from ns
1714
+ addRow(ns, le)
1715
+ lineTok = true; lineCont = true
1716
+ }
1717
+
1718
+ function addRow(from, to) {
1719
+ // build row with cells per micromark resolveTable semantics
1720
+ const row = {type: 'tableRow', children: [], position: {start: P(lineNo, ls, from), end: P(lineNo, ls, to)}}
1721
+ // tokenise: dividers and data
1722
+ // events list: [idx] -> {kind: 'd' divider | 'data', s, e}
1723
+ const ev = [null]
1724
+ let i = from
1725
+ while (i < to) {
1726
+ const c = src.charCodeAt(i)
1727
+ if (c === 124) { ev.push({k: 1, s: i, e: i + 1}); i++; continue }
1728
+ if (c === 32 || c === 9) { i++; continue }
1729
+ const s = i
1730
+ for (;;) {
1731
+ if (i >= to) break
1732
+ const d = src.charCodeAt(i)
1733
+ if (d === 124 || d === 32 || d === 9) break
1734
+ i++
1735
+ if (d === 92) { const e = src.charCodeAt(i); if ((e === 92 || e === 124) && i < to) i++ }
1736
+ }
1737
+ ev.push({k: 2, s, e: i})
1738
+ }
1739
+ // cells
1740
+ let inFirst = true
1741
+ let lastCell = [0, 0, 0, 0]
1742
+ let cell = [0, 1, 0, 0] // index+1 of row enter => first event index 1 (or row-exit)
1743
+ let cur = undefined
1744
+ const cells = []
1745
+ const pointOf = (idx, rowEnd) => idx === -1 ? to : ev[idx].s // starts
1746
+ // flush
1747
+ function flush(range, rowEnd) {
1748
+ // range[0]: index whose start point closes previous cell
1749
+ if (range[0] !== 0 && cur) cur.e = ev[range[0]] ? ev[range[0]].s : to
1750
+ const startP = range[1] <= ev.length - 1 ? ev[range[1]].s : from
1751
+ const c = {s: range[1] === 1 ? from : startP, e: startP}
1752
+ // first event after row enter starts at row start
1753
+ if (range[1] === 1) c.s = from
1754
+ if (range[2] !== 0) { c.ds = ev[range[2]].s; c.de = ev[range[3]].e }
1755
+ cells.push(c)
1756
+ cur = c
1757
+ if (rowEnd) { c.e = to; cur = undefined }
1758
+ }
1759
+ const n = ev.length - 1
1760
+ for (let idx = 1; idx <= n + 0; idx++) {
1761
+ const e = ev[idx]
1762
+ if (e.k === 2) {
1763
+ inFirst = false
1764
+ if (cell[2] === 0) {
1765
+ if (lastCell[1] !== 0) {
1766
+ cell[0] = cell[1]
1767
+ flush(lastCell, false)
1768
+ lastCell = [0, 0, 0, 0]
1769
+ }
1770
+ cell[2] = idx
1771
+ }
1772
+ cell[3] = idx
1773
+ } else {
1774
+ if (inFirst) inFirst = false
1775
+ else {
1776
+ if (lastCell[1] !== 0) {
1777
+ cell[0] = cell[1]
1778
+ flush(lastCell, false)
1779
+ }
1780
+ lastCell = cell
1781
+ cell = [lastCell[1], idx, 0, 0]
1782
+ }
1783
+ }
1784
+ }
1785
+ // row end
1786
+ if (lastCell[1] !== 0) {
1787
+ cell[0] = cell[1]
1788
+ flush(lastCell, true)
1789
+ } else if (cell[1] !== 0 && n >= 1) {
1790
+ flush(cell, true)
1791
+ }
1792
+ for (const c of cells) {
1793
+ const cn = {type: 'tableCell', children: [], position: {start: P(lineNo, ls, c.s), end: P(lineNo, ls, c.e)}}
1794
+ row.children.push(cn)
1795
+ if (doInline && c.ds !== undefined) pend.push(cn, 'table', lineNo, ls, c.ds, c.de, null, topNow())
1796
+ }
1797
+ tNode.children.push(row)
1798
+ tNode.position.end = P(lineNo, ls, to)
1799
+ }
1800
+
1801
+ // streaming entry point: parse a tail of the document (starting at a top-level safe point) and report what the
1802
+ // stream parser needs: the last safe point, definition / footnote ids by top-level index, root.checked log and the
1803
+ // phrasing nodes whose inline content depends on definition ids
1804
+ export function parseStream(source, opts) {
1805
+ const root = parse(source, {...opts, stream: true, tail: true})
1806
+ return {root, safe: streamSafe, defLog: defLog.slice(), fnLog: fnLog.slice(), rcLog: rcLog.slice(), entries: streamEntries, defIds, fnIds}
1807
+ }