Open History to see the last commit
1diff --git a/dev/lib/math-flow.js b/dev/lib/math-flow.js
2--- a/dev/lib/math-flow.js
3+++ b/dev/lib/math-flow.js
4@@ -4,7 +4,7 @@
6 import {ok as assert} from 'devlop'
7 import {factorySpace} from 'micromark-factory-space'
8-import {markdownLineEnding} from 'micromark-util-character'
9+import {markdownLineEnding, markdownSpace} from 'micromark-util-character'
10 import {codes, constants, types} from 'micromark-util-symbol'
12 /** @type {Construct} */
13@@ -32,6 +32,9 @@
14 ? tail[2].sliceSerialize(tail[1], true).length
15 : 0
16 let sizeOpen = 0
17+ let closed = false
18+ let slashes = 0
19+ let trailingAfterClose = false
21 return start
23@@ -79,7 +82,32 @@
24 }
26 effects.exit('mathFlowFenceSequence')
27- return factorySpace(effects, metaBefore, types.whitespace)(code)
28+
29+ return factorySpace(effects, afterOpeningWhitespace, types.whitespace)(code)
30+ }
31+
32+ /**
33+ * After opening fence whitespace, check for same-line content.
34+ *
35+ * ```markdown
36+ * > | $$ 1 + 1 $$
37+ * ^
38+ * ```
39+ *
40+ * @type {State}
41+ */
42+ function afterOpeningWhitespace(code) {
43+ if (code === codes.eof || markdownLineEnding(code)) {
44+ return metaAfter(code)
45+ }
46+
47+ effects.exit('mathFlowFence')
48+
49+ if (self.interrupt) {
50+ return ok(code)
51+ }
52+
53+ return beforeContentChunk(code)
54 }
56 /**
57@@ -230,6 +258,14 @@
58 )(code)
59 }
61+ if (code === codes.dollarSign) {
62+ return effects.attempt(
63+ {tokenize: tokenizeInlineClosingFence, partial: true},
64+ after,
65+ resumeContentAfterFailedClose
66+ )(code)
67+ }
68+
69 effects.enter('mathFlowValue')
70 return contentChunk(code)
71 }
72@@ -247,13 +283,74 @@
73 * @type {State}
74 */
75 function contentChunk(code) {
76+ const escaped = slashes % 2 === 1
77+ slashes = code === codes.backslash ? slashes + 1 : 0
78 if (code === codes.eof || markdownLineEnding(code)) {
79 effects.exit('mathFlowValue')
80 return beforeContentChunk(code)
81 }
83+ if (code === codes.dollarSign && !escaped) {
84+ effects.exit('mathFlowValue')
85+ return effects.attempt(
86+ {tokenize: tokenizeInlineClosingFence, partial: true},
87+ after,
88+ resumeContentAfterFailedClose
89+ )(code)
90+ }
91+
92 effects.consume(code)
93 return contentChunk
94+ }
95+
96+ /**
97+ * Resume content after a failed inline closing fence attempt.
98+ *
99+ * @type {State}
100+ */
101+ function resumeContentAfterFailedClose(code) {
102+ if (trailingAfterClose) return nok(code)
103+ effects.enter('mathFlowValue')
104+ effects.consume(code)
105+ return contentChunk
106+ }
107+
108+ /** @type {Tokenizer} */
109+ function tokenizeInlineClosingFence(effects, ok, nok) {
110+ let size = 0
111+ return sequenceStart
112+
113+ function sequenceStart(code) {
114+ effects.enter('mathFlowFence')
115+ effects.enter('mathFlowFenceSequence')
116+ return sequenceClose(code)
117+ }
118+
119+ function sequenceClose(code) {
120+ if (code === codes.dollarSign) {
121+ size++
122+ effects.consume(code)
123+ return sequenceClose
124+ }
125+
126+ if (size < sizeOpen) {
127+ return nok(code)
128+ }
129+
130+ effects.exit('mathFlowFenceSequence')
131+ return factorySpace(effects, afterSequenceClose, types.whitespace)(code)
132+ }
133+
134+ function afterSequenceClose(code) {
135+ if (code === codes.eof || markdownLineEnding(code)) {
136+ effects.exit('mathFlowFence')
137+ closed = true
138+ return ok(code)
139+ }
140+
141+ trailingAfterClose = true
142+ return nok(code)
143+ }
144 }
146 /**
147@@ -269,6 +366,7 @@
148 * @type {State}
149 */
150 function after(code) {
151+ if (!closed) return nok(code)
152 effects.exit('mathFlow')
153 return ok(code)
154 }
155@@ -357,6 +455,7 @@
156 function afterSequenceClose(code) {
157 if (code === codes.eof || markdownLineEnding(code)) {
158 effects.exit('mathFlowFence')
159+ closed = true
160 return ok(code)
161 }
163diff --git a/dev/lib/math-tex-flow.js b/dev/lib/math-tex-flow.js
164--- a/dev/lib/math-tex-flow.js
165+++ b/dev/lib/math-tex-flow.js
166@@ -26,6 +26,7 @@
167 */
168 function tokenizeMathFenced(effects, ok, nok) {
169 const self = this
170+ let closed = false
171 const tail = self.events[self.events.length - 1]
172 const initialSize =
173 tail && tail[1].type === types.linePrefix
174@@ -90,6 +91,7 @@
175 * @type {State}
176 */
177 function metaAfter(code) {
178+ if (code !== codes.eof && !markdownLineEnding(code)) return nok(code)
179 effects.exit('mathFlowFence')
181 if (self.interrupt) {
182@@ -245,6 +247,7 @@
183 * @type {State}
184 */
185 function after(code) {
186+ if (!closed) return nok(code)
187 effects.exit('mathFlow')
188 return ok(code)
189 }
190@@ -327,6 +330,7 @@
191 function afterSequenceClose(code) {
192 if (code === codes.eof || markdownLineEnding(code)) {
193 effects.exit('mathFlowFence')
194+ closed = true
195 return ok(code)
196 }
198diff --git a/dev/lib/math-tex-text.js b/dev/lib/math-tex-text.js
199--- a/dev/lib/math-tex-text.js
200+++ b/dev/lib/math-tex-text.js
201@@ -25,6 +25,7 @@
202 let token
203 /** @type {number} */
204 let openSequenceCode
205+ let substantive = false
207 return start
209@@ -83,7 +84,7 @@
210 * @type {State}
211 */
212 function between(code) {
213- if (code === codes.eof) {
214+ if (code === codes.eof || code === codes.graveAccent || (self.mathSingleLine && markdownLineEnding(code))) {
215 return nok(code)
216 }
218@@ -124,6 +125,8 @@
219 * @type {State}
220 */
221 function data(code) {
222+ if (code === codes.graveAccent) return nok(code)
223+ if (code !== codes.backslash && code !== codes.eof && code !== codes.space && code !== codes.comma && !markdownLineEnding(code) && !(code >= codes.digit0 && code <= codes.digit9)) substantive = true
224 if (
225 code === codes.eof ||
226 code === codes.space ||
227@@ -155,6 +158,7 @@
228 ? codes.rightParenthesis
229 : codes.rightSquareBracket
230 if (code === closeSequenceCode) {
231+ if (openSequenceCode === codes.leftSquareBracket && !substantive) return nok(code)
232 effects.consume(code)
233 effects.exit('mathTextSequence')
234 effects.exit('mathText')
235@@ -180,5 +184,8 @@
236 */
237 function previous(code) {
238 // If there is a previous code, there will always be a tail.
239- return code !== codes.backslash
240+ return (
241+ code !== codes.backslash ||
242+ this.events[this.events.length - 1][1].type === types.characterEscape
243+ )
244 }
245diff --git a/dev/lib/math-text.js b/dev/lib/math-text.js
246--- a/dev/lib/math-text.js
247+++ b/dev/lib/math-text.js
248@@ -10 +10 @@
249-import {markdownLineEnding} from 'micromark-util-character'
250+import {markdownLineEnding, markdownLineEndingOrSpace} from 'micromark-util-character'
251@@ -42,6 +42,13 @@
252 let size
253 /** @type {Token} */
254 let token
255+ /** @type {boolean} */
256+ let openWhitespace
257+ /** @type {number | undefined} */
258+ let charBeforeClose
259+ let slashes = 0
260+ let previousData
261+ let beforeData
263 return start
265@@ -89,6 +96,16 @@
266 }
268 effects.exit('mathTextSequence')
269+ // Reject if opening is followed by line ending
270+ if (markdownLineEnding(code)) {
271+ return nok(code)
272+ }
273+ // Track if opening sequence is followed by whitespace
274+ openWhitespace =
275+ code === codes.space ||
276+ code === codes.horizontalTab ||
277+ code === codes.comma
278+ if (sizeOpen === 1 && openWhitespace) return nok(code)
279 return between(code)
280 }
282@@ -103,13 +120,14 @@
283 * @type {State}
284 */
285 function between(code) {
286- if (code === codes.eof) {
287+ if (code === codes.eof || code === codes.graveAccent || ((sizeOpen === 1 || self.mathSingleLine) && markdownLineEnding(code))) {
288 return nok(code)
289 }
291 if (code === codes.dollarSign) {
292 token = effects.enter('mathTextSequence')
293 size = 0
294+ charBeforeClose = self.previous
295 return sequenceClose(code)
296 }
298@@ -144,6 +162,16 @@
299 * @type {State}
300 */
301 function data(code) {
302+ if (code === codes.graveAccent) return nok(code)
303+ if (code === codes.dollarSign && slashes % 2 === 1) {
304+ slashes = 0
305+ effects.consume(code)
306+ return data
307+ }
308+ if (code === codes.slash && previousData === codes.slash && beforeData === codes.colon) return nok(code)
309+ beforeData = previousData
310+ previousData = code
311+ slashes = code === codes.backslash ? slashes + 1 : 0
312 if (
313 code === codes.eof ||
314 code === codes.space ||
315@@ -179,9 +207,29 @@
317 // Done!
318 if (size === sizeOpen) {
319- effects.exit('mathTextSequence')
320- effects.exit('mathText')
321- return ok(code)
322+ // Reject if closing is preceded by line ending
323+ if (markdownLineEnding(charBeforeClose)) {
324+ return nok(code)
325+ }
326+ // Reject if closing is followed by a digit
327+ if (code !== null && code >= codes.digit0 && code <= codes.digit9) {
328+ return nok(code)
329+ }
330+ if (charBeforeClose === codes.comma && !openWhitespace && code !== codes.eof && !markdownLineEndingOrSpace(code) && !/[.,;:)\]}]/.test(String.fromCharCode(code))) {
331+ return nok(code)
332+ }
333+ // Check if closing sequence has matching whitespace pattern
334+ const closeWhitespace =
335+ charBeforeClose === codes.space ||
336+ charBeforeClose === codes.horizontalTab ||
337+ (charBeforeClose === codes.comma && openWhitespace)
338+ if (openWhitespace === closeWhitespace) {
339+ effects.exit('mathTextSequence')
340+ effects.exit('mathText')
341+ return ok(code)
342+ }
343+ // Whitespace mismatch: mark as data
344+ return nok(code)
345 }
347 // More or less accents: mark as data.
348diff --git a/package.json b/package.json
349--- a/package.json
350+++ b/package.json
351@@ -27,7 +27,7 @@
352 "type": "module",
353 "exports": {
354 "development": "./dev/index.js",
355- "default": "./index.js"
356+ "default": "./dev/index.js"
357 },
358 "files": [
359 "dev/",