math-flow.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /**
  2. * @import {Construct, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
  3. */
  4. import { factorySpace } from 'micromark-factory-space';
  5. import { markdownLineEnding } from 'micromark-util-character';
  6. /** @type {Construct} */
  7. export const mathFlow = {
  8. tokenize: tokenizeMathFenced,
  9. concrete: true,
  10. name: 'mathFlow'
  11. };
  12. /** @type {Construct} */
  13. const nonLazyContinuation = {
  14. tokenize: tokenizeNonLazyContinuation,
  15. partial: true
  16. };
  17. /**
  18. * @this {TokenizeContext}
  19. * @type {Tokenizer}
  20. */
  21. function tokenizeMathFenced(effects, ok, nok) {
  22. const self = this;
  23. const tail = self.events[self.events.length - 1];
  24. const initialSize = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
  25. let sizeOpen = 0;
  26. return start;
  27. /**
  28. * Start of math.
  29. *
  30. * ```markdown
  31. * > | $$
  32. * ^
  33. * | \frac{1}{2}
  34. * | $$
  35. * ```
  36. *
  37. * @type {State}
  38. */
  39. function start(code) {
  40. effects.enter('mathFlow');
  41. effects.enter('mathFlowFence');
  42. effects.enter('mathFlowFenceSequence');
  43. return sequenceOpen(code);
  44. }
  45. /**
  46. * In opening fence sequence.
  47. *
  48. * ```markdown
  49. * > | $$
  50. * ^
  51. * | \frac{1}{2}
  52. * | $$
  53. * ```
  54. *
  55. * @type {State}
  56. */
  57. function sequenceOpen(code) {
  58. if (code === 36) {
  59. effects.consume(code);
  60. sizeOpen++;
  61. return sequenceOpen;
  62. }
  63. if (sizeOpen < 2) {
  64. return nok(code);
  65. }
  66. effects.exit('mathFlowFenceSequence');
  67. return factorySpace(effects, metaBefore, "whitespace")(code);
  68. }
  69. /**
  70. * In opening fence, before meta.
  71. *
  72. * ```markdown
  73. * > | $$asciimath
  74. * ^
  75. * | x < y
  76. * | $$
  77. * ```
  78. *
  79. * @type {State}
  80. */
  81. function metaBefore(code) {
  82. if (code === null || markdownLineEnding(code)) {
  83. return metaAfter(code);
  84. }
  85. effects.enter('mathFlowFenceMeta');
  86. effects.enter("chunkString", {
  87. contentType: "string"
  88. });
  89. return meta(code);
  90. }
  91. /**
  92. * In meta.
  93. *
  94. * ```markdown
  95. * > | $$asciimath
  96. * ^
  97. * | x < y
  98. * | $$
  99. * ```
  100. *
  101. * @type {State}
  102. */
  103. function meta(code) {
  104. if (code === null || markdownLineEnding(code)) {
  105. effects.exit("chunkString");
  106. effects.exit('mathFlowFenceMeta');
  107. return metaAfter(code);
  108. }
  109. if (code === 36) {
  110. return nok(code);
  111. }
  112. effects.consume(code);
  113. return meta;
  114. }
  115. /**
  116. * After meta.
  117. *
  118. * ```markdown
  119. * > | $$
  120. * ^
  121. * | \frac{1}{2}
  122. * | $$
  123. * ```
  124. *
  125. * @type {State}
  126. */
  127. function metaAfter(code) {
  128. // Guaranteed to be eol/eof.
  129. effects.exit('mathFlowFence');
  130. if (self.interrupt) {
  131. return ok(code);
  132. }
  133. return effects.attempt(nonLazyContinuation, beforeNonLazyContinuation, after)(code);
  134. }
  135. /**
  136. * After eol/eof in math, at a non-lazy closing fence or content.
  137. *
  138. * ```markdown
  139. * | $$
  140. * > | \frac{1}{2}
  141. * ^
  142. * > | $$
  143. * ^
  144. * ```
  145. *
  146. * @type {State}
  147. */
  148. function beforeNonLazyContinuation(code) {
  149. return effects.attempt({
  150. tokenize: tokenizeClosingFence,
  151. partial: true
  152. }, after, contentStart)(code);
  153. }
  154. /**
  155. * Before math content, definitely not before a closing fence.
  156. *
  157. * ```markdown
  158. * | $$
  159. * > | \frac{1}{2}
  160. * ^
  161. * | $$
  162. * ```
  163. *
  164. * @type {State}
  165. */
  166. function contentStart(code) {
  167. return (initialSize ? factorySpace(effects, beforeContentChunk, "linePrefix", initialSize + 1) : beforeContentChunk)(code);
  168. }
  169. /**
  170. * Before math content, after optional prefix.
  171. *
  172. * ```markdown
  173. * | $$
  174. * > | \frac{1}{2}
  175. * ^
  176. * | $$
  177. * ```
  178. *
  179. * @type {State}
  180. */
  181. function beforeContentChunk(code) {
  182. if (code === null) {
  183. return after(code);
  184. }
  185. if (markdownLineEnding(code)) {
  186. return effects.attempt(nonLazyContinuation, beforeNonLazyContinuation, after)(code);
  187. }
  188. effects.enter('mathFlowValue');
  189. return contentChunk(code);
  190. }
  191. /**
  192. * In math content.
  193. *
  194. * ```markdown
  195. * | $$
  196. * > | \frac{1}{2}
  197. * ^
  198. * | $$
  199. * ```
  200. *
  201. * @type {State}
  202. */
  203. function contentChunk(code) {
  204. if (code === null || markdownLineEnding(code)) {
  205. effects.exit('mathFlowValue');
  206. return beforeContentChunk(code);
  207. }
  208. effects.consume(code);
  209. return contentChunk;
  210. }
  211. /**
  212. * After math (ha!).
  213. *
  214. * ```markdown
  215. * | $$
  216. * | \frac{1}{2}
  217. * > | $$
  218. * ^
  219. * ```
  220. *
  221. * @type {State}
  222. */
  223. function after(code) {
  224. effects.exit('mathFlow');
  225. return ok(code);
  226. }
  227. /** @type {Tokenizer} */
  228. function tokenizeClosingFence(effects, ok, nok) {
  229. let size = 0;
  230. /**
  231. * Before closing fence, at optional whitespace.
  232. *
  233. * ```markdown
  234. * | $$
  235. * | \frac{1}{2}
  236. * > | $$
  237. * ^
  238. * ```
  239. */
  240. return factorySpace(effects, beforeSequenceClose, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4);
  241. /**
  242. * In closing fence, after optional whitespace, at sequence.
  243. *
  244. * ```markdown
  245. * | $$
  246. * | \frac{1}{2}
  247. * > | $$
  248. * ^
  249. * ```
  250. *
  251. * @type {State}
  252. */
  253. function beforeSequenceClose(code) {
  254. effects.enter('mathFlowFence');
  255. effects.enter('mathFlowFenceSequence');
  256. return sequenceClose(code);
  257. }
  258. /**
  259. * In closing fence sequence.
  260. *
  261. * ```markdown
  262. * | $$
  263. * | \frac{1}{2}
  264. * > | $$
  265. * ^
  266. * ```
  267. *
  268. * @type {State}
  269. */
  270. function sequenceClose(code) {
  271. if (code === 36) {
  272. size++;
  273. effects.consume(code);
  274. return sequenceClose;
  275. }
  276. if (size < sizeOpen) {
  277. return nok(code);
  278. }
  279. effects.exit('mathFlowFenceSequence');
  280. return factorySpace(effects, afterSequenceClose, "whitespace")(code);
  281. }
  282. /**
  283. * After closing fence sequence, after optional whitespace.
  284. *
  285. * ```markdown
  286. * | $$
  287. * | \frac{1}{2}
  288. * > | $$
  289. * ^
  290. * ```
  291. *
  292. * @type {State}
  293. */
  294. function afterSequenceClose(code) {
  295. if (code === null || markdownLineEnding(code)) {
  296. effects.exit('mathFlowFence');
  297. return ok(code);
  298. }
  299. return nok(code);
  300. }
  301. }
  302. }
  303. /**
  304. * @this {TokenizeContext}
  305. * @type {Tokenizer}
  306. */
  307. function tokenizeNonLazyContinuation(effects, ok, nok) {
  308. const self = this;
  309. return start;
  310. /** @type {State} */
  311. function start(code) {
  312. if (code === null) {
  313. return ok(code);
  314. }
  315. effects.enter("lineEnding");
  316. effects.consume(code);
  317. effects.exit("lineEnding");
  318. return lineStart;
  319. }
  320. /** @type {State} */
  321. function lineStart(code) {
  322. return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
  323. }
  324. }