1/*
2 * MD4C: Markdown parser for C
3 * (http://github.com/mity/md4c)
4 *
5 * Copyright (c) 2016-2024 Martin Mitáš
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation
10 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 * and/or sell copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23 * IN THE SOFTWARE.
24 */
25
26#include "md4c.h"
27
28#include <limits.h>
29#include <stdint.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33
34
35/*****************************
36 *** Miscellaneous Stuff ***
37 *****************************/
38
39#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199409L
40 /* C89/90 or old compilers in general may not understand "inline". */
41 #if defined __GNUC__
42 #define inline __inline__
43 #elif defined _MSC_VER
44 #define inline __inline
45 #else
46 #define inline
47 #endif
48#endif
49
50/* Make the UTF-8 support the default. */
51#if !defined MD4C_USE_ASCII && !defined MD4C_USE_UTF8 && !defined MD4C_USE_UTF16
52 #define MD4C_USE_UTF8
53#endif
54
55/* Magic for making wide literals with MD4C_USE_UTF16. */
56#ifdef _T
57 #undef _T
58#endif
59#if defined MD4C_USE_UTF16
60 #define _T(x) L##x
61#else
62 #define _T(x) x
63#endif
64
65/* Misc. macros. */
66#define SIZEOF_ARRAY(a) (sizeof(a) / sizeof(a[0]))
67
68#define STRINGIZE_(x) #x
69#define STRINGIZE(x) STRINGIZE_(x)
70
71#define MAX(a,b) ((a) > (b) ? (a) : (b))
72#define MIN(a,b) ((a) < (b) ? (a) : (b))
73
74#ifndef TRUE
75 #define TRUE 1
76 #define FALSE 0
77#endif
78
79#define MD_LOG(msg) \
80 do { \
81 if(ctx->parser.debug_log != NULL) \
82 ctx->parser.debug_log((msg), ctx->userdata); \
83 } while(0)
84
85#ifdef DEBUG
86 #define MD_ASSERT(cond) \
87 do { \
88 if(!(cond)) { \
89 MD_LOG(__FILE__ ":" STRINGIZE(__LINE__) ": " \
90 "Assertion '" STRINGIZE(cond) "' failed."); \
91 exit(1); \
92 } \
93 } while(0)
94
95 #define MD_UNREACHABLE() MD_ASSERT(1 == 0)
96#else
97 #ifdef __GNUC__
98 #define MD_ASSERT(cond) do { if(!(cond)) __builtin_unreachable(); } while(0)
99 #define MD_UNREACHABLE() do { __builtin_unreachable(); } while(0)
100 #elif defined _MSC_VER && _MSC_VER > 120
101 #define MD_ASSERT(cond) do { __assume(cond); } while(0)
102 #define MD_UNREACHABLE() do { __assume(0); } while(0)
103 #else
104 #define MD_ASSERT(cond) do {} while(0)
105 #define MD_UNREACHABLE() do {} while(0)
106 #endif
107#endif
108
109/* For falling through case labels in switch statements. */
110#if defined __clang__ && __clang_major__ >= 12
111 #define MD_FALLTHROUGH() __attribute__((fallthrough))
112#elif defined __GNUC__ && __GNUC__ >= 7
113 #define MD_FALLTHROUGH() __attribute__((fallthrough))
114#else
115 #define MD_FALLTHROUGH() ((void)0)
116#endif
117
118/* Suppress "unused parameter" warnings. */
119#define MD_UNUSED(x) ((void)x)
120
121
122/******************************
123 *** Some internal limits ***
124 ******************************/
125
126/* We limit code span marks to lower than 32 backticks. This solves the
127 * pathologic case of too many openers, each of different length: Their
128 * resolving would be then O(n^2). */
129#define CODESPAN_MARK_MAXLEN 32
130
131/* We limit column count of tables to prevent quadratic explosion of output
132 * from pathological input of a table thousands of columns and thousands
133 * of rows where rows are requested with as little as single character
134 * per-line, relying on us to "helpfully" fill all the missing "<td></td>". */
135#define TABLE_MAXCOLCOUNT 128
136
137
138/************************
139 *** Internal Types ***
140 ************************/
141
142/* These are omnipresent so lets save some typing. */
143#define CHAR MD_CHAR
144#define SZ MD_SIZE
145#define OFF MD_OFFSET
146
147#define SZ_MAX (sizeof(SZ) == 8 ? UINT64_MAX : UINT32_MAX)
148#define OFF_MAX (sizeof(OFF) == 8 ? UINT64_MAX : UINT32_MAX)
149
150typedef struct MD_MARK_tag MD_MARK;
151typedef struct MD_BLOCK_tag MD_BLOCK;
152typedef struct MD_CONTAINER_tag MD_CONTAINER;
153typedef struct MD_REF_DEF_tag MD_REF_DEF;
154
155
156/* During analyzes of inline marks, we need to manage stacks of unresolved
157 * openers of the given type.
158 * The stack connects the marks via MD_MARK::next;
159 */
160typedef struct MD_MARKSTACK_tag MD_MARKSTACK;
161struct MD_MARKSTACK_tag {
162 int top; /* -1 if empty. */
163};
164
165/* Context propagated through all the parsing. */
166typedef struct MD_CTX_tag MD_CTX;
167struct MD_CTX_tag {
168 /* Immutable stuff (parameters of md_parse()). */
169 const CHAR* text;
170 SZ size;
171 MD_PARSER parser;
172 void* userdata;
173
174 /* When this is true, it allows some optimizations. */
175 int doc_ends_with_newline;
176
177 /* Helper temporary growing buffer. */
178 CHAR* buffer;
179 unsigned alloc_buffer;
180
181 /* Reference definitions. */
182 MD_REF_DEF* ref_defs;
183 int n_ref_defs;
184 int alloc_ref_defs;
185 void** ref_def_hashtable;
186 int ref_def_hashtable_size;
187 SZ max_ref_def_output;
188
189 /* Stack of inline/span markers.
190 * This is only used for parsing a single block contents but by storing it
191 * here we may reuse the stack for subsequent blocks; i.e. we have fewer
192 * (re)allocations. */
193 MD_MARK* marks;
194 int n_marks;
195 int alloc_marks;
196
197#if defined MD4C_USE_UTF16
198 char mark_char_map[128];
199#else
200 char mark_char_map[256];
201#endif
202
203 /* For resolving of inline spans. */
204 MD_MARKSTACK opener_stacks[16];
205#define ASTERISK_OPENERS_oo_mod3_0 (ctx->opener_stacks[0]) /* Opener-only */
206#define ASTERISK_OPENERS_oo_mod3_1 (ctx->opener_stacks[1])
207#define ASTERISK_OPENERS_oo_mod3_2 (ctx->opener_stacks[2])
208#define ASTERISK_OPENERS_oc_mod3_0 (ctx->opener_stacks[3]) /* Both opener and closer candidate */
209#define ASTERISK_OPENERS_oc_mod3_1 (ctx->opener_stacks[4])
210#define ASTERISK_OPENERS_oc_mod3_2 (ctx->opener_stacks[5])
211#define UNDERSCORE_OPENERS_oo_mod3_0 (ctx->opener_stacks[6]) /* Opener-only */
212#define UNDERSCORE_OPENERS_oo_mod3_1 (ctx->opener_stacks[7])
213#define UNDERSCORE_OPENERS_oo_mod3_2 (ctx->opener_stacks[8])
214#define UNDERSCORE_OPENERS_oc_mod3_0 (ctx->opener_stacks[9]) /* Both opener and closer candidate */
215#define UNDERSCORE_OPENERS_oc_mod3_1 (ctx->opener_stacks[10])
216#define UNDERSCORE_OPENERS_oc_mod3_2 (ctx->opener_stacks[11])
217#define TILDE_OPENERS_1 (ctx->opener_stacks[12])
218#define TILDE_OPENERS_2 (ctx->opener_stacks[13])
219#define BRACKET_OPENERS (ctx->opener_stacks[14])
220#define DOLLAR_OPENERS (ctx->opener_stacks[15])
221
222 /* Stack of dummies which need to call free() for pointers stored in them.
223 * These are constructed during inline parsing and freed after all the block
224 * is processed (i.e. all callbacks referring those strings are called). */
225 MD_MARKSTACK ptr_stack;
226
227 /* For resolving table rows. */
228 int n_table_cell_boundaries;
229 int table_cell_boundaries_head;
230 int table_cell_boundaries_tail;
231
232 /* For resolving links. */
233 int unresolved_link_head;
234 int unresolved_link_tail;
235
236 /* For resolving raw HTML. */
237 OFF html_comment_horizon;
238 OFF html_proc_instr_horizon;
239 OFF html_decl_horizon;
240 OFF html_cdata_horizon;
241
242 /* For block analysis.
243 * Notes:
244 * -- It holds MD_BLOCK as well as MD_LINE structures. After each
245 * MD_BLOCK, its (multiple) MD_LINE(s) follow.
246 * -- For MD_BLOCK_HTML and MD_BLOCK_CODE, MD_VERBATIMLINE(s) are used
247 * instead of MD_LINE(s).
248 */
249 void* block_bytes;
250 MD_BLOCK* current_block;
251 int n_block_bytes;
252 int alloc_block_bytes;
253
254 /* For container block analysis. */
255 MD_CONTAINER* containers;
256 int n_containers;
257 int alloc_containers;
258
259 /* Minimal indentation to call the block "indented code block". */
260 unsigned code_indent_offset;
261
262 /* Contextual info for line analysis. */
263 SZ code_fence_length; /* For checking closing fence length. */
264 int html_block_type; /* For checking closing raw HTML condition. */
265 int last_line_has_list_loosening_effect;
266 int last_list_item_starts_with_two_blank_lines;
267};
268
269enum MD_LINETYPE_tag {
270 MD_LINE_BLANK,
271 MD_LINE_HR,
272 MD_LINE_ATXHEADER,
273 MD_LINE_SETEXTHEADER,
274 MD_LINE_SETEXTUNDERLINE,
275 MD_LINE_INDENTEDCODE,
276 MD_LINE_FENCEDCODE,
277 MD_LINE_HTML,
278 MD_LINE_TEXT,
279 MD_LINE_TABLE,
280 MD_LINE_TABLEUNDERLINE
281};
282typedef enum MD_LINETYPE_tag MD_LINETYPE;
283
284typedef struct MD_LINE_ANALYSIS_tag MD_LINE_ANALYSIS;
285struct MD_LINE_ANALYSIS_tag {
286 MD_LINETYPE type;
287 unsigned data;
288 int enforce_new_block;
289 OFF beg;
290 OFF end;
291 unsigned indent; /* Indentation level. */
292};
293
294typedef struct MD_LINE_tag MD_LINE;
295struct MD_LINE_tag {
296 OFF beg;
297 OFF end;
298};
299
300typedef struct MD_VERBATIMLINE_tag MD_VERBATIMLINE;
301struct MD_VERBATIMLINE_tag {
302 OFF beg;
303 OFF end;
304 OFF indent;
305};
306
307
308/*****************
309 *** Helpers ***
310 *****************/
311
312/* Character accessors. */
313#define CH(off) (ctx->text[(off)])
314#define STR(off) (ctx->text + (off))
315
316/* Character classification.
317 * Note we assume ASCII compatibility of code points < 128 here. */
318#define ISIN_(ch, ch_min, ch_max) ((ch_min) <= (unsigned)(ch) && (unsigned)(ch) <= (ch_max))
319#define ISANYOF_(ch, palette) ((ch) != _T('\0') && md_strchr((palette), (ch)) != NULL)
320#define ISANYOF2_(ch, ch1, ch2) ((ch) == (ch1) || (ch) == (ch2))
321#define ISANYOF3_(ch, ch1, ch2, ch3) ((ch) == (ch1) || (ch) == (ch2) || (ch) == (ch3))
322#define ISASCII_(ch) ((unsigned)(ch) <= 127)
323#define ISBLANK_(ch) (ISANYOF2_((ch), _T(' '), _T('\t')))
324#define ISNEWLINE_(ch) (ISANYOF2_((ch), _T('\r'), _T('\n')))
325#define ISWHITESPACE_(ch) (ISBLANK_(ch) || ISANYOF2_((ch), _T('\v'), _T('\f')))
326#define ISCNTRL_(ch) ((unsigned)(ch) <= 31 || (unsigned)(ch) == 127)
327#define ISPUNCT_(ch) (ISIN_(ch, 33, 47) || ISIN_(ch, 58, 64) || ISIN_(ch, 91, 96) || ISIN_(ch, 123, 126))
328#define ISUPPER_(ch) (ISIN_(ch, _T('A'), _T('Z')))
329#define ISLOWER_(ch) (ISIN_(ch, _T('a'), _T('z')))
330#define ISALPHA_(ch) (ISUPPER_(ch) || ISLOWER_(ch))
331#define ISDIGIT_(ch) (ISIN_(ch, _T('0'), _T('9')))
332#define ISXDIGIT_(ch) (ISDIGIT_(ch) || ISIN_(ch, _T('A'), _T('F')) || ISIN_(ch, _T('a'), _T('f')))
333#define ISALNUM_(ch) (ISALPHA_(ch) || ISDIGIT_(ch))
334
335#define ISANYOF(off, palette) ISANYOF_(CH(off), (palette))
336#define ISANYOF2(off, ch1, ch2) ISANYOF2_(CH(off), (ch1), (ch2))
337#define ISANYOF3(off, ch1, ch2, ch3) ISANYOF3_(CH(off), (ch1), (ch2), (ch3))
338#define ISASCII(off) ISASCII_(CH(off))
339#define ISBLANK(off) ISBLANK_(CH(off))
340#define ISNEWLINE(off) ISNEWLINE_(CH(off))
341#define ISWHITESPACE(off) ISWHITESPACE_(CH(off))
342#define ISCNTRL(off) ISCNTRL_(CH(off))
343#define ISPUNCT(off) ISPUNCT_(CH(off))
344#define ISUPPER(off) ISUPPER_(CH(off))
345#define ISLOWER(off) ISLOWER_(CH(off))
346#define ISALPHA(off) ISALPHA_(CH(off))
347#define ISDIGIT(off) ISDIGIT_(CH(off))
348#define ISXDIGIT(off) ISXDIGIT_(CH(off))
349#define ISALNUM(off) ISALNUM_(CH(off))
350
351
352#if defined MD4C_USE_UTF16
353 #define md_strchr wcschr
354#else
355 #define md_strchr strchr
356#endif
357
358
359/* Case insensitive check of string equality. */
360static inline int
361md_ascii_case_eq(const CHAR* s1, const CHAR* s2, SZ n)
362{
363 OFF i;
364 for(i = 0; i < n; i++) {
365 CHAR ch1 = s1[i];
366 CHAR ch2 = s2[i];
367
368 if(ISLOWER_(ch1))
369 ch1 += ('A'-'a');
370 if(ISLOWER_(ch2))
371 ch2 += ('A'-'a');
372 if(ch1 != ch2)
373 return FALSE;
374 }
375 return TRUE;
376}
377
378static inline int
379md_ascii_eq(const CHAR* s1, const CHAR* s2, SZ n)
380{
381 return memcmp(s1, s2, n * sizeof(CHAR)) == 0;
382}
383
384static int
385md_text_with_null_replacement(MD_CTX* ctx, MD_TEXTTYPE type, const CHAR* str, SZ size)
386{
387 OFF off = 0;
388 int ret = 0;
389
390 while(1) {
391 while(off < size && str[off] != _T('\0'))
392 off++;
393
394 if(off > 0) {
395 ret = ctx->parser.text(type, str, off, ctx->userdata);
396 if(ret != 0)
397 return ret;
398
399 str += off;
400 size -= off;
401 off = 0;
402 }
403
404 if(off >= size)
405 return 0;
406
407 ret = ctx->parser.text(MD_TEXT_NULLCHAR, _T(""), 1, ctx->userdata);
408 if(ret != 0)
409 return ret;
410 off++;
411 }
412}
413
414
415#define MD_CHECK(func) \
416 do { \
417 ret = (func); \
418 if(ret < 0) \
419 goto abort; \
420 } while(0)
421
422
423#define MD_TEMP_BUFFER(sz) \
424 do { \
425 if(sz > ctx->alloc_buffer) { \
426 CHAR* new_buffer; \
427 SZ new_size = ((sz) + (sz) / 2 + 128) & ~127; \
428 \
429 new_buffer = realloc(ctx->buffer, new_size); \
430 if(new_buffer == NULL) { \
431 MD_LOG("realloc() failed."); \
432 ret = -1; \
433 goto abort; \
434 } \
435 \
436 ctx->buffer = new_buffer; \
437 ctx->alloc_buffer = new_size; \
438 } \
439 } while(0)
440
441
442#define MD_ENTER_BLOCK(type, arg) \
443 do { \
444 ret = ctx->parser.enter_block((type), (arg), ctx->userdata); \
445 if(ret != 0) { \
446 MD_LOG("Aborted from enter_block() callback."); \
447 goto abort; \
448 } \
449 } while(0)
450
451#define MD_LEAVE_BLOCK(type, arg) \
452 do { \
453 ret = ctx->parser.leave_block((type), (arg), ctx->userdata); \
454 if(ret != 0) { \
455 MD_LOG("Aborted from leave_block() callback."); \
456 goto abort; \
457 } \
458 } while(0)
459
460#define MD_ENTER_SPAN(type, arg) \
461 do { \
462 ret = ctx->parser.enter_span((type), (arg), ctx->userdata); \
463 if(ret != 0) { \
464 MD_LOG("Aborted from enter_span() callback."); \
465 goto abort; \
466 } \
467 } while(0)
468
469#define MD_LEAVE_SPAN(type, arg) \
470 do { \
471 ret = ctx->parser.leave_span((type), (arg), ctx->userdata); \
472 if(ret != 0) { \
473 MD_LOG("Aborted from leave_span() callback."); \
474 goto abort; \
475 } \
476 } while(0)
477
478#define MD_TEXT(type, str, size) \
479 do { \
480 if(size > 0) { \
481 ret = ctx->parser.text((type), (str), (size), ctx->userdata); \
482 if(ret != 0) { \
483 MD_LOG("Aborted from text() callback."); \
484 goto abort; \
485 } \
486 } \
487 } while(0)
488
489#define MD_TEXT_INSECURE(type, str, size) \
490 do { \
491 if(size > 0) { \
492 ret = md_text_with_null_replacement(ctx, type, str, size); \
493 if(ret != 0) { \
494 MD_LOG("Aborted from text() callback."); \
495 goto abort; \
496 } \
497 } \
498 } while(0)
499
500
501/* If the offset falls into a gap between line, we return the following
502 * line. */
503static const MD_LINE*
504md_lookup_line(OFF off, const MD_LINE* lines, MD_SIZE n_lines, MD_SIZE* p_line_index)
505{
506 MD_SIZE lo, hi;
507 MD_SIZE pivot;
508 const MD_LINE* line;
509
510 lo = 0;
511 hi = n_lines - 1;
512 while(lo <= hi) {
513 pivot = (lo + hi) / 2;
514 line = &lines[pivot];
515
516 if(off < line->beg) {
517 if(hi == 0 || lines[hi-1].end < off) {
518 if(p_line_index != NULL)
519 *p_line_index = pivot;
520 return line;
521 }
522 hi = pivot - 1;
523 } else if(off > line->end) {
524 lo = pivot + 1;
525 } else {
526 if(p_line_index != NULL)
527 *p_line_index = pivot;
528 return line;
529 }
530 }
531
532 return NULL;
533}
534
535
536/*************************
537 *** Unicode Support ***
538 *************************/
539
540typedef struct MD_UNICODE_FOLD_INFO_tag MD_UNICODE_FOLD_INFO;
541struct MD_UNICODE_FOLD_INFO_tag {
542 unsigned codepoints[3];
543 unsigned n_codepoints;
544};
545
546
547#if defined MD4C_USE_UTF16 || defined MD4C_USE_UTF8
548 /* Binary search over sorted "map" of codepoints. Consecutive sequences
549 * of codepoints may be encoded in the map by just using the
550 * (MIN_CODEPOINT | 0x40000000) and (MAX_CODEPOINT | 0x80000000).
551 *
552 * Returns index of the found record in the map (in the case of ranges,
553 * the minimal value is used); or -1 on failure. */
554 static int
555 md_unicode_bsearch__(unsigned codepoint, const unsigned* map, size_t map_size)
556 {
557 int beg, end;
558 int pivot_beg, pivot_end;
559
560 beg = 0;
561 end = (int) map_size-1;
562 while(beg <= end) {
563 /* Pivot may be a range, not just a single value. */
564 pivot_beg = pivot_end = (beg + end) / 2;
565 if(map[pivot_end] & 0x40000000)
566 pivot_end++;
567 if(map[pivot_beg] & 0x80000000)
568 pivot_beg--;
569
570 if(codepoint < (map[pivot_beg] & 0x00ffffff))
571 end = pivot_beg - 1;
572 else if(codepoint > (map[pivot_end] & 0x00ffffff))
573 beg = pivot_end + 1;
574 else
575 return pivot_beg;
576 }
577
578 return -1;
579 }
580
581 static int
582 md_is_unicode_whitespace__(unsigned codepoint)
583 {
584#define R(cp_min, cp_max) ((cp_min) | 0x40000000), ((cp_max) | 0x80000000)
585#define S(cp) (cp)
586 /* Unicode "Zs" category.
587 * (generated by scripts/build_whitespace_map.py) */
588 static const unsigned WHITESPACE_MAP[] = {
589 S(0x0020), S(0x00a0), S(0x1680), R(0x2000,0x200a), S(0x202f), S(0x205f), S(0x3000)
590 };
591#undef R
592#undef S
593
594 /* The ASCII ones are the most frequently used ones, also CommonMark
595 * specification requests few more in this range. */
596 if(codepoint <= 0x7f)
597 return ISWHITESPACE_(codepoint);
598
599 return (md_unicode_bsearch__(codepoint, WHITESPACE_MAP, SIZEOF_ARRAY(WHITESPACE_MAP)) >= 0);
600 }
601
602 static int
603 md_is_unicode_punct__(unsigned codepoint)
604 {
605#define R(cp_min, cp_max) ((cp_min) | 0x40000000), ((cp_max) | 0x80000000)
606#define S(cp) (cp)
607 /* Unicode general "P" and "S" categories.
608 * (generated by scripts/build_punct_map.py) */
609 static const unsigned PUNCT_MAP[] = {
610 R(0x0021,0x002f), R(0x003a,0x0040), R(0x005b,0x0060), R(0x007b,0x007e), R(0x00a1,0x00a9),
611 R(0x00ab,0x00ac), R(0x00ae,0x00b1), S(0x00b4), R(0x00b6,0x00b8), S(0x00bb), S(0x00bf), S(0x00d7),
612 S(0x00f7), R(0x02c2,0x02c5), R(0x02d2,0x02df), R(0x02e5,0x02eb), S(0x02ed), R(0x02ef,0x02ff), S(0x0375),
613 S(0x037e), R(0x0384,0x0385), S(0x0387), S(0x03f6), S(0x0482), R(0x055a,0x055f), R(0x0589,0x058a),
614 R(0x058d,0x058f), S(0x05be), S(0x05c0), S(0x05c3), S(0x05c6), R(0x05f3,0x05f4), R(0x0606,0x060f),
615 S(0x061b), R(0x061d,0x061f), R(0x066a,0x066d), S(0x06d4), S(0x06de), S(0x06e9), R(0x06fd,0x06fe),
616 R(0x0700,0x070d), R(0x07f6,0x07f9), R(0x07fe,0x07ff), R(0x0830,0x083e), S(0x085e), S(0x0888),
617 R(0x0964,0x0965), S(0x0970), R(0x09f2,0x09f3), R(0x09fa,0x09fb), S(0x09fd), S(0x0a76), R(0x0af0,0x0af1),
618 S(0x0b70), R(0x0bf3,0x0bfa), S(0x0c77), S(0x0c7f), S(0x0c84), S(0x0d4f), S(0x0d79), S(0x0df4), S(0x0e3f),
619 S(0x0e4f), R(0x0e5a,0x0e5b), R(0x0f01,0x0f17), R(0x0f1a,0x0f1f), S(0x0f34), S(0x0f36), S(0x0f38),
620 R(0x0f3a,0x0f3d), S(0x0f85), R(0x0fbe,0x0fc5), R(0x0fc7,0x0fcc), R(0x0fce,0x0fda), R(0x104a,0x104f),
621 R(0x109e,0x109f), S(0x10fb), R(0x1360,0x1368), R(0x1390,0x1399), S(0x1400), R(0x166d,0x166e),
622 R(0x169b,0x169c), R(0x16eb,0x16ed), R(0x1735,0x1736), R(0x17d4,0x17d6), R(0x17d8,0x17db),
623 R(0x1800,0x180a), S(0x1940), R(0x1944,0x1945), R(0x19de,0x19ff), R(0x1a1e,0x1a1f), R(0x1aa0,0x1aa6),
624 R(0x1aa8,0x1aad), R(0x1b5a,0x1b6a), R(0x1b74,0x1b7e), R(0x1bfc,0x1bff), R(0x1c3b,0x1c3f),
625 R(0x1c7e,0x1c7f), R(0x1cc0,0x1cc7), S(0x1cd3), S(0x1fbd), R(0x1fbf,0x1fc1), R(0x1fcd,0x1fcf),
626 R(0x1fdd,0x1fdf), R(0x1fed,0x1fef), R(0x1ffd,0x1ffe), R(0x2010,0x2027), R(0x2030,0x205e),
627 R(0x207a,0x207e), R(0x208a,0x208e), R(0x20a0,0x20c0), R(0x2100,0x2101), R(0x2103,0x2106),
628 R(0x2108,0x2109), S(0x2114), R(0x2116,0x2118), R(0x211e,0x2123), S(0x2125), S(0x2127), S(0x2129),
629 S(0x212e), R(0x213a,0x213b), R(0x2140,0x2144), R(0x214a,0x214d), S(0x214f), R(0x218a,0x218b),
630 R(0x2190,0x2426), R(0x2440,0x244a), R(0x249c,0x24e9), R(0x2500,0x2775), R(0x2794,0x2b73),
631 R(0x2b76,0x2b95), R(0x2b97,0x2bff), R(0x2ce5,0x2cea), R(0x2cf9,0x2cfc), R(0x2cfe,0x2cff), S(0x2d70),
632 R(0x2e00,0x2e2e), R(0x2e30,0x2e5d), R(0x2e80,0x2e99), R(0x2e9b,0x2ef3), R(0x2f00,0x2fd5),
633 R(0x2ff0,0x2fff), R(0x3001,0x3004), R(0x3008,0x3020), S(0x3030), R(0x3036,0x3037), R(0x303d,0x303f),
634 R(0x309b,0x309c), S(0x30a0), S(0x30fb), R(0x3190,0x3191), R(0x3196,0x319f), R(0x31c0,0x31e3), S(0x31ef),
635 R(0x3200,0x321e), R(0x322a,0x3247), S(0x3250), R(0x3260,0x327f), R(0x328a,0x32b0), R(0x32c0,0x33ff),
636 R(0x4dc0,0x4dff), R(0xa490,0xa4c6), R(0xa4fe,0xa4ff), R(0xa60d,0xa60f), S(0xa673), S(0xa67e),
637 R(0xa6f2,0xa6f7), R(0xa700,0xa716), R(0xa720,0xa721), R(0xa789,0xa78a), R(0xa828,0xa82b),
638 R(0xa836,0xa839), R(0xa874,0xa877), R(0xa8ce,0xa8cf), R(0xa8f8,0xa8fa), S(0xa8fc), R(0xa92e,0xa92f),
639 S(0xa95f), R(0xa9c1,0xa9cd), R(0xa9de,0xa9df), R(0xaa5c,0xaa5f), R(0xaa77,0xaa79), R(0xaade,0xaadf),
640 R(0xaaf0,0xaaf1), S(0xab5b), R(0xab6a,0xab6b), S(0xabeb), S(0xfb29), R(0xfbb2,0xfbc2), R(0xfd3e,0xfd4f),
641 S(0xfdcf), R(0xfdfc,0xfdff), R(0xfe10,0xfe19), R(0xfe30,0xfe52), R(0xfe54,0xfe66), R(0xfe68,0xfe6b),
642 R(0xff01,0xff0f), R(0xff1a,0xff20), R(0xff3b,0xff40), R(0xff5b,0xff65), R(0xffe0,0xffe6),
643 R(0xffe8,0xffee), R(0xfffc,0xfffd), R(0x10100,0x10102), R(0x10137,0x1013f), R(0x10179,0x10189),
644 R(0x1018c,0x1018e), R(0x10190,0x1019c), S(0x101a0), R(0x101d0,0x101fc), S(0x1039f), S(0x103d0),
645 S(0x1056f), S(0x10857), R(0x10877,0x10878), S(0x1091f), S(0x1093f), R(0x10a50,0x10a58), S(0x10a7f),
646 S(0x10ac8), R(0x10af0,0x10af6), R(0x10b39,0x10b3f), R(0x10b99,0x10b9c), S(0x10ead), R(0x10f55,0x10f59),
647 R(0x10f86,0x10f89), R(0x11047,0x1104d), R(0x110bb,0x110bc), R(0x110be,0x110c1), R(0x11140,0x11143),
648 R(0x11174,0x11175), R(0x111c5,0x111c8), S(0x111cd), S(0x111db), R(0x111dd,0x111df), R(0x11238,0x1123d),
649 S(0x112a9), R(0x1144b,0x1144f), R(0x1145a,0x1145b), S(0x1145d), S(0x114c6), R(0x115c1,0x115d7),
650 R(0x11641,0x11643), R(0x11660,0x1166c), S(0x116b9), R(0x1173c,0x1173f), S(0x1183b), R(0x11944,0x11946),
651 S(0x119e2), R(0x11a3f,0x11a46), R(0x11a9a,0x11a9c), R(0x11a9e,0x11aa2), R(0x11b00,0x11b09),
652 R(0x11c41,0x11c45), R(0x11c70,0x11c71), R(0x11ef7,0x11ef8), R(0x11f43,0x11f4f), R(0x11fd5,0x11ff1),
653 S(0x11fff), R(0x12470,0x12474), R(0x12ff1,0x12ff2), R(0x16a6e,0x16a6f), S(0x16af5), R(0x16b37,0x16b3f),
654 R(0x16b44,0x16b45), R(0x16e97,0x16e9a), S(0x16fe2), S(0x1bc9c), S(0x1bc9f), R(0x1cf50,0x1cfc3),
655 R(0x1d000,0x1d0f5), R(0x1d100,0x1d126), R(0x1d129,0x1d164), R(0x1d16a,0x1d16c), R(0x1d183,0x1d184),
656 R(0x1d18c,0x1d1a9), R(0x1d1ae,0x1d1ea), R(0x1d200,0x1d241), S(0x1d245), R(0x1d300,0x1d356), S(0x1d6c1),
657 S(0x1d6db), S(0x1d6fb), S(0x1d715), S(0x1d735), S(0x1d74f), S(0x1d76f), S(0x1d789), S(0x1d7a9),
658 S(0x1d7c3), R(0x1d800,0x1d9ff), R(0x1da37,0x1da3a), R(0x1da6d,0x1da74), R(0x1da76,0x1da83),
659 R(0x1da85,0x1da8b), S(0x1e14f), S(0x1e2ff), R(0x1e95e,0x1e95f), S(0x1ecac), S(0x1ecb0), S(0x1ed2e),
660 R(0x1eef0,0x1eef1), R(0x1f000,0x1f02b), R(0x1f030,0x1f093), R(0x1f0a0,0x1f0ae), R(0x1f0b1,0x1f0bf),
661 R(0x1f0c1,0x1f0cf), R(0x1f0d1,0x1f0f5), R(0x1f10d,0x1f1ad), R(0x1f1e6,0x1f202), R(0x1f210,0x1f23b),
662 R(0x1f240,0x1f248), R(0x1f250,0x1f251), R(0x1f260,0x1f265), R(0x1f300,0x1f6d7), R(0x1f6dc,0x1f6ec),
663 R(0x1f6f0,0x1f6fc), R(0x1f700,0x1f776), R(0x1f77b,0x1f7d9), R(0x1f7e0,0x1f7eb), S(0x1f7f0),
664 R(0x1f800,0x1f80b), R(0x1f810,0x1f847), R(0x1f850,0x1f859), R(0x1f860,0x1f887), R(0x1f890,0x1f8ad),
665 R(0x1f8b0,0x1f8b1), R(0x1f900,0x1fa53), R(0x1fa60,0x1fa6d), R(0x1fa70,0x1fa7c), R(0x1fa80,0x1fa88),
666 R(0x1fa90,0x1fabd), R(0x1fabf,0x1fac5), R(0x1face,0x1fadb), R(0x1fae0,0x1fae8), R(0x1faf0,0x1faf8),
667 R(0x1fb00,0x1fb92), R(0x1fb94,0x1fbca)
668 };
669#undef R
670#undef S
671
672 /* The ASCII ones are the most frequently used ones, also CommonMark
673 * specification requests few more in this range. */
674 if(codepoint <= 0x7f)
675 return ISPUNCT_(codepoint);
676
677 return (md_unicode_bsearch__(codepoint, PUNCT_MAP, SIZEOF_ARRAY(PUNCT_MAP)) >= 0);
678 }
679
680 static void
681 md_get_unicode_fold_info(unsigned codepoint, MD_UNICODE_FOLD_INFO* info)
682 {
683#define R(cp_min, cp_max) ((cp_min) | 0x40000000), ((cp_max) | 0x80000000)
684#define S(cp) (cp)
685 /* Unicode "Pc", "Pd", "Pe", "Pf", "Pi", "Po", "Ps" categories.
686 * (generated by scripts/build_folding_map.py) */
687 static const unsigned FOLD_MAP_1[] = {
688 R(0x0041,0x005a), S(0x00b5), R(0x00c0,0x00d6), R(0x00d8,0x00de), R(0x0100,0x012e), R(0x0132,0x0136),
689 R(0x0139,0x0147), R(0x014a,0x0176), S(0x0178), R(0x0179,0x017d), S(0x017f), S(0x0181), S(0x0182),
690 S(0x0184), S(0x0186), S(0x0187), S(0x0189), S(0x018a), S(0x018b), S(0x018e), S(0x018f), S(0x0190),
691 S(0x0191), S(0x0193), S(0x0194), S(0x0196), S(0x0197), S(0x0198), S(0x019c), S(0x019d), S(0x019f),
692 R(0x01a0,0x01a4), S(0x01a6), S(0x01a7), S(0x01a9), S(0x01ac), S(0x01ae), S(0x01af), S(0x01b1), S(0x01b2),
693 S(0x01b3), S(0x01b5), S(0x01b7), S(0x01b8), S(0x01bc), S(0x01c4), S(0x01c5), S(0x01c7), S(0x01c8),
694 S(0x01ca), R(0x01cb,0x01db), R(0x01de,0x01ee), S(0x01f1), S(0x01f2), S(0x01f4), S(0x01f6), S(0x01f7),
695 R(0x01f8,0x021e), S(0x0220), R(0x0222,0x0232), S(0x023a), S(0x023b), S(0x023d), S(0x023e), S(0x0241),
696 S(0x0243), S(0x0244), S(0x0245), R(0x0246,0x024e), S(0x0345), S(0x0370), S(0x0372), S(0x0376), S(0x037f),
697 S(0x0386), R(0x0388,0x038a), S(0x038c), S(0x038e), S(0x038f), R(0x0391,0x03a1), R(0x03a3,0x03ab),
698 S(0x03c2), S(0x03cf), S(0x03d0), S(0x03d1), S(0x03d5), S(0x03d6), R(0x03d8,0x03ee), S(0x03f0), S(0x03f1),
699 S(0x03f4), S(0x03f5), S(0x03f7), S(0x03f9), S(0x03fa), R(0x03fd,0x03ff), R(0x0400,0x040f),
700 R(0x0410,0x042f), R(0x0460,0x0480), R(0x048a,0x04be), S(0x04c0), R(0x04c1,0x04cd), R(0x04d0,0x052e),
701 R(0x0531,0x0556), R(0x10a0,0x10c5), S(0x10c7), S(0x10cd), R(0x13f8,0x13fd), S(0x1c80), S(0x1c81),
702 S(0x1c82), S(0x1c83), S(0x1c84), S(0x1c85), S(0x1c86), S(0x1c87), S(0x1c88), R(0x1c90,0x1cba),
703 R(0x1cbd,0x1cbf), R(0x1e00,0x1e94), S(0x1e9b), R(0x1ea0,0x1efe), R(0x1f08,0x1f0f), R(0x1f18,0x1f1d),
704 R(0x1f28,0x1f2f), R(0x1f38,0x1f3f), R(0x1f48,0x1f4d), S(0x1f59), S(0x1f5b), S(0x1f5d), S(0x1f5f),
705 R(0x1f68,0x1f6f), S(0x1fb8), S(0x1fb9), S(0x1fba), S(0x1fbb), S(0x1fbe), R(0x1fc8,0x1fcb), S(0x1fd8),
706 S(0x1fd9), S(0x1fda), S(0x1fdb), S(0x1fe8), S(0x1fe9), S(0x1fea), S(0x1feb), S(0x1fec), S(0x1ff8),
707 S(0x1ff9), S(0x1ffa), S(0x1ffb), S(0x2126), S(0x212a), S(0x212b), S(0x2132), R(0x2160,0x216f), S(0x2183),
708 R(0x24b6,0x24cf), R(0x2c00,0x2c2f), S(0x2c60), S(0x2c62), S(0x2c63), S(0x2c64), R(0x2c67,0x2c6b),
709 S(0x2c6d), S(0x2c6e), S(0x2c6f), S(0x2c70), S(0x2c72), S(0x2c75), S(0x2c7e), S(0x2c7f), R(0x2c80,0x2ce2),
710 S(0x2ceb), S(0x2ced), S(0x2cf2), R(0xa640,0xa66c), R(0xa680,0xa69a), R(0xa722,0xa72e), R(0xa732,0xa76e),
711 S(0xa779), S(0xa77b), S(0xa77d), R(0xa77e,0xa786), S(0xa78b), S(0xa78d), S(0xa790), S(0xa792),
712 R(0xa796,0xa7a8), S(0xa7aa), S(0xa7ab), S(0xa7ac), S(0xa7ad), S(0xa7ae), S(0xa7b0), S(0xa7b1), S(0xa7b2),
713 S(0xa7b3), R(0xa7b4,0xa7c2), S(0xa7c4), S(0xa7c5), S(0xa7c6), S(0xa7c7), S(0xa7c9), S(0xa7d0), S(0xa7d6),
714 S(0xa7d8), S(0xa7f5), R(0xab70,0xabbf), R(0xff21,0xff3a), R(0x10400,0x10427), R(0x104b0,0x104d3),
715 R(0x10570,0x1057a), R(0x1057c,0x1058a), R(0x1058c,0x10592), S(0x10594), S(0x10595), R(0x10c80,0x10cb2),
716 R(0x118a0,0x118bf), R(0x16e40,0x16e5f), R(0x1e900,0x1e921)
717 };
718 static const unsigned FOLD_MAP_1_DATA[] = {
719 0x0061, 0x007a, 0x03bc, 0x00e0, 0x00f6, 0x00f8, 0x00fe, 0x0101, 0x012f, 0x0133, 0x0137, 0x013a, 0x0148,
720 0x014b, 0x0177, 0x00ff, 0x017a, 0x017e, 0x0073, 0x0253, 0x0183, 0x0185, 0x0254, 0x0188, 0x0256, 0x0257,
721 0x018c, 0x01dd, 0x0259, 0x025b, 0x0192, 0x0260, 0x0263, 0x0269, 0x0268, 0x0199, 0x026f, 0x0272, 0x0275,
722 0x01a1, 0x01a5, 0x0280, 0x01a8, 0x0283, 0x01ad, 0x0288, 0x01b0, 0x028a, 0x028b, 0x01b4, 0x01b6, 0x0292,
723 0x01b9, 0x01bd, 0x01c6, 0x01c6, 0x01c9, 0x01c9, 0x01cc, 0x01cc, 0x01dc, 0x01df, 0x01ef, 0x01f3, 0x01f3,
724 0x01f5, 0x0195, 0x01bf, 0x01f9, 0x021f, 0x019e, 0x0223, 0x0233, 0x2c65, 0x023c, 0x019a, 0x2c66, 0x0242,
725 0x0180, 0x0289, 0x028c, 0x0247, 0x024f, 0x03b9, 0x0371, 0x0373, 0x0377, 0x03f3, 0x03ac, 0x03ad, 0x03af,
726 0x03cc, 0x03cd, 0x03ce, 0x03b1, 0x03c1, 0x03c3, 0x03cb, 0x03c3, 0x03d7, 0x03b2, 0x03b8, 0x03c6, 0x03c0,
727 0x03d9, 0x03ef, 0x03ba, 0x03c1, 0x03b8, 0x03b5, 0x03f8, 0x03f2, 0x03fb, 0x037b, 0x037d, 0x0450, 0x045f,
728 0x0430, 0x044f, 0x0461, 0x0481, 0x048b, 0x04bf, 0x04cf, 0x04c2, 0x04ce, 0x04d1, 0x052f, 0x0561, 0x0586,
729 0x2d00, 0x2d25, 0x2d27, 0x2d2d, 0x13f0, 0x13f5, 0x0432, 0x0434, 0x043e, 0x0441, 0x0442, 0x0442, 0x044a,
730 0x0463, 0xa64b, 0x10d0, 0x10fa, 0x10fd, 0x10ff, 0x1e01, 0x1e95, 0x1e61, 0x1ea1, 0x1eff, 0x1f00, 0x1f07,
731 0x1f10, 0x1f15, 0x1f20, 0x1f27, 0x1f30, 0x1f37, 0x1f40, 0x1f45, 0x1f51, 0x1f53, 0x1f55, 0x1f57, 0x1f60,
732 0x1f67, 0x1fb0, 0x1fb1, 0x1f70, 0x1f71, 0x03b9, 0x1f72, 0x1f75, 0x1fd0, 0x1fd1, 0x1f76, 0x1f77, 0x1fe0,
733 0x1fe1, 0x1f7a, 0x1f7b, 0x1fe5, 0x1f78, 0x1f79, 0x1f7c, 0x1f7d, 0x03c9, 0x006b, 0x00e5, 0x214e, 0x2170,
734 0x217f, 0x2184, 0x24d0, 0x24e9, 0x2c30, 0x2c5f, 0x2c61, 0x026b, 0x1d7d, 0x027d, 0x2c68, 0x2c6c, 0x0251,
735 0x0271, 0x0250, 0x0252, 0x2c73, 0x2c76, 0x023f, 0x0240, 0x2c81, 0x2ce3, 0x2cec, 0x2cee, 0x2cf3, 0xa641,
736 0xa66d, 0xa681, 0xa69b, 0xa723, 0xa72f, 0xa733, 0xa76f, 0xa77a, 0xa77c, 0x1d79, 0xa77f, 0xa787, 0xa78c,
737 0x0265, 0xa791, 0xa793, 0xa797, 0xa7a9, 0x0266, 0x025c, 0x0261, 0x026c, 0x026a, 0x029e, 0x0287, 0x029d,
738 0xab53, 0xa7b5, 0xa7c3, 0xa794, 0x0282, 0x1d8e, 0xa7c8, 0xa7ca, 0xa7d1, 0xa7d7, 0xa7d9, 0xa7f6, 0x13a0,
739 0x13ef, 0xff41, 0xff5a, 0x10428, 0x1044f, 0x104d8, 0x104fb, 0x10597, 0x105a1, 0x105a3, 0x105b1, 0x105b3,
740 0x105b9, 0x105bb, 0x105bc, 0x10cc0, 0x10cf2, 0x118c0, 0x118df, 0x16e60, 0x16e7f, 0x1e922, 0x1e943
741 };
742 static const unsigned FOLD_MAP_2[] = {
743 S(0x00df), S(0x0130), S(0x0149), S(0x01f0), S(0x0587), S(0x1e96), S(0x1e97), S(0x1e98), S(0x1e99),
744 S(0x1e9a), S(0x1e9e), S(0x1f50), R(0x1f80,0x1f87), R(0x1f88,0x1f8f), R(0x1f90,0x1f97), R(0x1f98,0x1f9f),
745 R(0x1fa0,0x1fa7), R(0x1fa8,0x1faf), S(0x1fb2), S(0x1fb3), S(0x1fb4), S(0x1fb6), S(0x1fbc), S(0x1fc2),
746 S(0x1fc3), S(0x1fc4), S(0x1fc6), S(0x1fcc), S(0x1fd6), S(0x1fe4), S(0x1fe6), S(0x1ff2), S(0x1ff3),
747 S(0x1ff4), S(0x1ff6), S(0x1ffc), S(0xfb00), S(0xfb01), S(0xfb02), S(0xfb05), S(0xfb06), S(0xfb13),
748 S(0xfb14), S(0xfb15), S(0xfb16), S(0xfb17)
749 };
750 static const unsigned FOLD_MAP_2_DATA[] = {
751 0x0073,0x0073, 0x0069,0x0307, 0x02bc,0x006e, 0x006a,0x030c, 0x0565,0x0582, 0x0068,0x0331, 0x0074,0x0308,
752 0x0077,0x030a, 0x0079,0x030a, 0x0061,0x02be, 0x0073,0x0073, 0x03c5,0x0313, 0x1f00,0x03b9, 0x1f07,0x03b9,
753 0x1f00,0x03b9, 0x1f07,0x03b9, 0x1f20,0x03b9, 0x1f27,0x03b9, 0x1f20,0x03b9, 0x1f27,0x03b9, 0x1f60,0x03b9,
754 0x1f67,0x03b9, 0x1f60,0x03b9, 0x1f67,0x03b9, 0x1f70,0x03b9, 0x03b1,0x03b9, 0x03ac,0x03b9, 0x03b1,0x0342,
755 0x03b1,0x03b9, 0x1f74,0x03b9, 0x03b7,0x03b9, 0x03ae,0x03b9, 0x03b7,0x0342, 0x03b7,0x03b9, 0x03b9,0x0342,
756 0x03c1,0x0313, 0x03c5,0x0342, 0x1f7c,0x03b9, 0x03c9,0x03b9, 0x03ce,0x03b9, 0x03c9,0x0342, 0x03c9,0x03b9,
757 0x0066,0x0066, 0x0066,0x0069, 0x0066,0x006c, 0x0073,0x0074, 0x0073,0x0074, 0x0574,0x0576, 0x0574,0x0565,
758 0x0574,0x056b, 0x057e,0x0576, 0x0574,0x056d
759 };
760 static const unsigned FOLD_MAP_3[] = {
761 S(0x0390), S(0x03b0), S(0x1f52), S(0x1f54), S(0x1f56), S(0x1fb7), S(0x1fc7), S(0x1fd2), S(0x1fd3),
762 S(0x1fd7), S(0x1fe2), S(0x1fe3), S(0x1fe7), S(0x1ff7), S(0xfb03), S(0xfb04)
763 };
764 static const unsigned FOLD_MAP_3_DATA[] = {
765 0x03b9,0x0308,0x0301, 0x03c5,0x0308,0x0301, 0x03c5,0x0313,0x0300, 0x03c5,0x0313,0x0301,
766 0x03c5,0x0313,0x0342, 0x03b1,0x0342,0x03b9, 0x03b7,0x0342,0x03b9, 0x03b9,0x0308,0x0300,
767 0x03b9,0x0308,0x0301, 0x03b9,0x0308,0x0342, 0x03c5,0x0308,0x0300, 0x03c5,0x0308,0x0301,
768 0x03c5,0x0308,0x0342, 0x03c9,0x0342,0x03b9, 0x0066,0x0066,0x0069, 0x0066,0x0066,0x006c
769 };
770#undef R
771#undef S
772 static const struct {
773 const unsigned* map;
774 const unsigned* data;
775 size_t map_size;
776 unsigned n_codepoints;
777 } FOLD_MAP_LIST[] = {
778 { FOLD_MAP_1, FOLD_MAP_1_DATA, SIZEOF_ARRAY(FOLD_MAP_1), 1 },
779 { FOLD_MAP_2, FOLD_MAP_2_DATA, SIZEOF_ARRAY(FOLD_MAP_2), 2 },
780 { FOLD_MAP_3, FOLD_MAP_3_DATA, SIZEOF_ARRAY(FOLD_MAP_3), 3 }
781 };
782
783 int i;
784
785 /* Fast path for ASCII characters. */
786 if(codepoint <= 0x7f) {
787 info->codepoints[0] = codepoint;
788 if(ISUPPER_(codepoint))
789 info->codepoints[0] += 'a' - 'A';
790 info->n_codepoints = 1;
791 return;
792 }
793
794 /* Try to locate the codepoint in any of the maps. */
795 for(i = 0; i < (int) SIZEOF_ARRAY(FOLD_MAP_LIST); i++) {
796 int index;
797
798 index = md_unicode_bsearch__(codepoint, FOLD_MAP_LIST[i].map, FOLD_MAP_LIST[i].map_size);
799 if(index >= 0) {
800 /* Found the mapping. */
801 unsigned n_codepoints = FOLD_MAP_LIST[i].n_codepoints;
802 const unsigned* map = FOLD_MAP_LIST[i].map;
803 const unsigned* codepoints = FOLD_MAP_LIST[i].data + (index * n_codepoints);
804
805 memcpy(info->codepoints, codepoints, sizeof(unsigned) * n_codepoints);
806 info->n_codepoints = n_codepoints;
807
808 if(FOLD_MAP_LIST[i].map[index] != codepoint) {
809 /* The found mapping maps whole range of codepoints,
810 * i.e. we have to offset info->codepoints[0] accordingly. */
811 if((map[index] & 0x00ffffff)+1 == codepoints[0]) {
812 /* Alternating type of the range. */
813 info->codepoints[0] = codepoint + ((codepoint & 0x1) == (map[index] & 0x1) ? 1 : 0);
814 } else {
815 /* Range to range kind of mapping. */
816 info->codepoints[0] += (codepoint - (map[index] & 0x00ffffff));
817 }
818 }
819
820 return;
821 }
822 }
823
824 /* No mapping found. Map the codepoint to itself. */
825 info->codepoints[0] = codepoint;
826 info->n_codepoints = 1;
827 }
828#endif
829
830
831#if defined MD4C_USE_UTF16
832 #define IS_UTF16_SURROGATE_HI(word) (((WORD)(word) & 0xfc00) == 0xd800)
833 #define IS_UTF16_SURROGATE_LO(word) (((WORD)(word) & 0xfc00) == 0xdc00)
834 #define UTF16_DECODE_SURROGATE(hi, lo) (0x10000 + ((((unsigned)(hi) & 0x3ff) << 10) | (((unsigned)(lo) & 0x3ff) << 0)))
835
836 static unsigned
837 md_decode_utf16le__(const CHAR* str, SZ str_size, SZ* p_size)
838 {
839 if(IS_UTF16_SURROGATE_HI(str[0])) {
840 if(1 < str_size && IS_UTF16_SURROGATE_LO(str[1])) {
841 if(p_size != NULL)
842 *p_size = 2;
843 return UTF16_DECODE_SURROGATE(str[0], str[1]);
844 }
845 }
846
847 if(p_size != NULL)
848 *p_size = 1;
849 return str[0];
850 }
851
852 static unsigned
853 md_decode_utf16le_before__(MD_CTX* ctx, OFF off)
854 {
855 if(off > 2 && IS_UTF16_SURROGATE_HI(CH(off-2)) && IS_UTF16_SURROGATE_LO(CH(off-1)))
856 return UTF16_DECODE_SURROGATE(CH(off-2), CH(off-1));
857
858 return CH(off);
859 }
860
861 /* No whitespace uses surrogates, so no decoding needed here. */
862 #define ISUNICODEWHITESPACE_(codepoint) md_is_unicode_whitespace__(codepoint)
863 #define ISUNICODEWHITESPACE(off) md_is_unicode_whitespace__(CH(off))
864 #define ISUNICODEWHITESPACEBEFORE(off) md_is_unicode_whitespace__(CH((off)-1))
865
866 #define ISUNICODEPUNCT(off) md_is_unicode_punct__(md_decode_utf16le__(STR(off), ctx->size - (off), NULL))
867 #define ISUNICODEPUNCTBEFORE(off) md_is_unicode_punct__(md_decode_utf16le_before__(ctx, off))
868
869 static inline int
870 md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_char_size)
871 {
872 return md_decode_utf16le__(str+off, str_size-off, p_char_size);
873 }
874#elif defined MD4C_USE_UTF8
875 #define IS_UTF8_LEAD1(byte) ((unsigned char)(byte) <= 0x7f)
876 #define IS_UTF8_LEAD2(byte) (((unsigned char)(byte) & 0xe0) == 0xc0)
877 #define IS_UTF8_LEAD3(byte) (((unsigned char)(byte) & 0xf0) == 0xe0)
878 #define IS_UTF8_LEAD4(byte) (((unsigned char)(byte) & 0xf8) == 0xf0)
879 #define IS_UTF8_TAIL(byte) (((unsigned char)(byte) & 0xc0) == 0x80)
880
881 static unsigned
882 md_decode_utf8__(const CHAR* str, SZ str_size, SZ* p_size)
883 {
884 if(!IS_UTF8_LEAD1(str[0])) {
885 if(IS_UTF8_LEAD2(str[0])) {
886 if(1 < str_size && IS_UTF8_TAIL(str[1])) {
887 if(p_size != NULL)
888 *p_size = 2;
889
890 return (((unsigned int)str[0] & 0x1f) << 6) |
891 (((unsigned int)str[1] & 0x3f) << 0);
892 }
893 } else if(IS_UTF8_LEAD3(str[0])) {
894 if(2 < str_size && IS_UTF8_TAIL(str[1]) && IS_UTF8_TAIL(str[2])) {
895 if(p_size != NULL)
896 *p_size = 3;
897
898 return (((unsigned int)str[0] & 0x0f) << 12) |
899 (((unsigned int)str[1] & 0x3f) << 6) |
900 (((unsigned int)str[2] & 0x3f) << 0);
901 }
902 } else if(IS_UTF8_LEAD4(str[0])) {
903 if(3 < str_size && IS_UTF8_TAIL(str[1]) && IS_UTF8_TAIL(str[2]) && IS_UTF8_TAIL(str[3])) {
904 if(p_size != NULL)
905 *p_size = 4;
906
907 return (((unsigned int)str[0] & 0x07) << 18) |
908 (((unsigned int)str[1] & 0x3f) << 12) |
909 (((unsigned int)str[2] & 0x3f) << 6) |
910 (((unsigned int)str[3] & 0x3f) << 0);
911 }
912 }
913 }
914
915 if(p_size != NULL)
916 *p_size = 1;
917 return (unsigned) str[0];
918 }
919
920 static unsigned
921 md_decode_utf8_before__(MD_CTX* ctx, OFF off)
922 {
923 if(!IS_UTF8_LEAD1(CH(off-1))) {
924 if(off > 1 && IS_UTF8_LEAD2(CH(off-2)) && IS_UTF8_TAIL(CH(off-1)))
925 return (((unsigned int)CH(off-2) & 0x1f) << 6) |
926 (((unsigned int)CH(off-1) & 0x3f) << 0);
927
928 if(off > 2 && IS_UTF8_LEAD3(CH(off-3)) && IS_UTF8_TAIL(CH(off-2)) && IS_UTF8_TAIL(CH(off-1)))
929 return (((unsigned int)CH(off-3) & 0x0f) << 12) |
930 (((unsigned int)CH(off-2) & 0x3f) << 6) |
931 (((unsigned int)CH(off-1) & 0x3f) << 0);
932
933 if(off > 3 && IS_UTF8_LEAD4(CH(off-4)) && IS_UTF8_TAIL(CH(off-3)) && IS_UTF8_TAIL(CH(off-2)) && IS_UTF8_TAIL(CH(off-1)))
934 return (((unsigned int)CH(off-4) & 0x07) << 18) |
935 (((unsigned int)CH(off-3) & 0x3f) << 12) |
936 (((unsigned int)CH(off-2) & 0x3f) << 6) |
937 (((unsigned int)CH(off-1) & 0x3f) << 0);
938 }
939
940 return (unsigned) CH(off-1);
941 }
942
943 #define ISUNICODEWHITESPACE_(codepoint) md_is_unicode_whitespace__(codepoint)
944 #define ISUNICODEWHITESPACE(off) md_is_unicode_whitespace__(md_decode_utf8__(STR(off), ctx->size - (off), NULL))
945 #define ISUNICODEWHITESPACEBEFORE(off) md_is_unicode_whitespace__(md_decode_utf8_before__(ctx, off))
946
947 #define ISUNICODEPUNCT(off) md_is_unicode_punct__(md_decode_utf8__(STR(off), ctx->size - (off), NULL))
948 #define ISUNICODEPUNCTBEFORE(off) md_is_unicode_punct__(md_decode_utf8_before__(ctx, off))
949
950 static inline unsigned
951 md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_char_size)
952 {
953 return md_decode_utf8__(str+off, str_size-off, p_char_size);
954 }
955#else
956 #define ISUNICODEWHITESPACE_(codepoint) ISWHITESPACE_(codepoint)
957 #define ISUNICODEWHITESPACE(off) ISWHITESPACE(off)
958 #define ISUNICODEWHITESPACEBEFORE(off) ISWHITESPACE((off)-1)
959
960 #define ISUNICODEPUNCT(off) ISPUNCT(off)
961 #define ISUNICODEPUNCTBEFORE(off) ISPUNCT((off)-1)
962
963 static inline void
964 md_get_unicode_fold_info(unsigned codepoint, MD_UNICODE_FOLD_INFO* info)
965 {
966 info->codepoints[0] = codepoint;
967 if(ISUPPER_(codepoint))
968 info->codepoints[0] += 'a' - 'A';
969 info->n_codepoints = 1;
970 }
971
972 static inline unsigned
973 md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_size)
974 {
975 *p_size = 1;
976 return (unsigned) str[off];
977 }
978#endif
979
980
981/*************************************
982 *** Helper string manipulations ***
983 *************************************/
984
985/* Fill buffer with copy of the string between 'beg' and 'end' but replace any
986 * line breaks with given replacement character.
987 *
988 * NOTE: Caller is responsible to make sure the buffer is large enough.
989 * (Given the output is always shorter than input, (end - beg) is good idea
990 * what the caller should allocate.)
991 */
992static void
993md_merge_lines(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, MD_SIZE n_lines,
994 CHAR line_break_replacement_char, CHAR* buffer, SZ* p_size)
995{
996 CHAR* ptr = buffer;
997 int line_index = 0;
998 OFF off = beg;
999
1000 MD_UNUSED(n_lines);
1001
1002 while(1) {
1003 const MD_LINE* line = &lines[line_index];
1004 OFF line_end = line->end;
1005 if(end < line_end)
1006 line_end = end;
1007
1008 while(off < line_end) {
1009 *ptr = CH(off);
1010 ptr++;
1011 off++;
1012 }
1013
1014 if(off >= end) {
1015 *p_size = (MD_SIZE)(ptr - buffer);
1016 return;
1017 }
1018
1019 *ptr = line_break_replacement_char;
1020 ptr++;
1021
1022 line_index++;
1023 off = lines[line_index].beg;
1024 }
1025}
1026
1027/* Wrapper of md_merge_lines() which allocates new buffer for the output string.
1028 */
1029static int
1030md_merge_lines_alloc(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, MD_SIZE n_lines,
1031 CHAR line_break_replacement_char, CHAR** p_str, SZ* p_size)
1032{
1033 CHAR* buffer;
1034
1035 buffer = (CHAR*) malloc(sizeof(CHAR) * (end - beg));
1036 if(buffer == NULL) {
1037 MD_LOG("malloc() failed.");
1038 return -1;
1039 }
1040
1041 md_merge_lines(ctx, beg, end, lines, n_lines,
1042 line_break_replacement_char, buffer, p_size);
1043
1044 *p_str = buffer;
1045 return 0;
1046}
1047
1048static OFF
1049md_skip_unicode_whitespace(const CHAR* label, OFF off, SZ size)
1050{
1051 SZ char_size;
1052 unsigned codepoint;
1053
1054 while(off < size) {
1055 codepoint = md_decode_unicode(label, off, size, &char_size);
1056 if(!ISUNICODEWHITESPACE_(codepoint) && !ISNEWLINE_(label[off]))
1057 break;
1058 off += char_size;
1059 }
1060
1061 return off;
1062}
1063
1064
1065/******************************
1066 *** Recognizing raw HTML ***
1067 ******************************/
1068
1069/* md_is_html_tag() may be called when processing inlines (inline raw HTML)
1070 * or when breaking document to blocks (checking for start of HTML block type 7).
1071 *
1072 * When breaking document to blocks, we do not yet know line boundaries, but
1073 * in that case the whole tag has to live on a single line. We distinguish this
1074 * by n_lines == 0.
1075 */
1076static int
1077md_is_html_tag(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1078{
1079 int attr_state;
1080 OFF off = beg;
1081 OFF line_end = (n_lines > 0) ? lines[0].end : ctx->size;
1082 MD_SIZE line_index = 0;
1083
1084 MD_ASSERT(CH(beg) == _T('<'));
1085
1086 if(off + 1 >= line_end)
1087 return FALSE;
1088 off++;
1089
1090 /* For parsing attributes, we need a little state automaton below.
1091 * State -1: no attributes are allowed.
1092 * State 0: attribute could follow after some whitespace.
1093 * State 1: after a whitespace (attribute name may follow).
1094 * State 2: after attribute name ('=' MAY follow).
1095 * State 3: after '=' (value specification MUST follow).
1096 * State 41: in middle of unquoted attribute value.
1097 * State 42: in middle of single-quoted attribute value.
1098 * State 43: in middle of double-quoted attribute value.
1099 */
1100 attr_state = 0;
1101
1102 if(CH(off) == _T('/')) {
1103 /* Closer tag "</ ... >". No attributes may be present. */
1104 attr_state = -1;
1105 off++;
1106 }
1107
1108 /* Tag name */
1109 if(off >= line_end || !ISALPHA(off))
1110 return FALSE;
1111 off++;
1112 while(off < line_end && (ISALNUM(off) || CH(off) == _T('-')))
1113 off++;
1114
1115 /* (Optional) attributes (if not closer), (optional) '/' (if not closer)
1116 * and final '>'. */
1117 while(1) {
1118 while(off < line_end && !ISNEWLINE(off)) {
1119 if(attr_state > 40) {
1120 if(attr_state == 41 && (ISBLANK(off) || ISANYOF(off, _T("\"'=<>`")))) {
1121 attr_state = 0;
1122 off--; /* Put the char back for re-inspection in the new state. */
1123 } else if(attr_state == 42 && CH(off) == _T('\'')) {
1124 attr_state = 0;
1125 } else if(attr_state == 43 && CH(off) == _T('"')) {
1126 attr_state = 0;
1127 }
1128 off++;
1129 } else if(ISWHITESPACE(off)) {
1130 if(attr_state == 0)
1131 attr_state = 1;
1132 off++;
1133 } else if(attr_state <= 2 && CH(off) == _T('>')) {
1134 /* End. */
1135 goto done;
1136 } else if(attr_state <= 2 && CH(off) == _T('/') && off+1 < line_end && CH(off+1) == _T('>')) {
1137 /* End with digraph '/>' */
1138 off++;
1139 goto done;
1140 } else if((attr_state == 1 || attr_state == 2) && (ISALPHA(off) || CH(off) == _T('_') || CH(off) == _T(':'))) {
1141 off++;
1142 /* Attribute name */
1143 while(off < line_end && (ISALNUM(off) || ISANYOF(off, _T("_.:-"))))
1144 off++;
1145 attr_state = 2;
1146 } else if(attr_state == 2 && CH(off) == _T('=')) {
1147 /* Attribute assignment sign */
1148 off++;
1149 attr_state = 3;
1150 } else if(attr_state == 3) {
1151 /* Expecting start of attribute value. */
1152 if(CH(off) == _T('"'))
1153 attr_state = 43;
1154 else if(CH(off) == _T('\''))
1155 attr_state = 42;
1156 else if(!ISANYOF(off, _T("\"'=<>`")) && !ISNEWLINE(off))
1157 attr_state = 41;
1158 else
1159 return FALSE;
1160 off++;
1161 } else {
1162 /* Anything unexpected. */
1163 return FALSE;
1164 }
1165 }
1166
1167 /* We have to be on a single line. See definition of start condition
1168 * of HTML block, type 7. */
1169 if(n_lines == 0)
1170 return FALSE;
1171
1172 line_index++;
1173 if(line_index >= n_lines)
1174 return FALSE;
1175
1176 off = lines[line_index].beg;
1177 line_end = lines[line_index].end;
1178
1179 if(attr_state == 0 || attr_state == 41)
1180 attr_state = 1;
1181
1182 if(off >= max_end)
1183 return FALSE;
1184 }
1185
1186done:
1187 if(off >= max_end)
1188 return FALSE;
1189
1190 *p_end = off+1;
1191 return TRUE;
1192}
1193
1194static int
1195md_scan_for_html_closer(MD_CTX* ctx, const MD_CHAR* str, MD_SIZE len,
1196 const MD_LINE* lines, MD_SIZE n_lines,
1197 OFF beg, OFF max_end, OFF* p_end,
1198 OFF* p_scan_horizon)
1199{
1200 OFF off = beg;
1201 MD_SIZE line_index = 0;
1202
1203 if(off < *p_scan_horizon && *p_scan_horizon >= max_end - len) {
1204 /* We have already scanned the range up to the max_end so we know
1205 * there is nothing to see. */
1206 return FALSE;
1207 }
1208
1209 while(TRUE) {
1210 while(off + len <= lines[line_index].end && off + len <= max_end) {
1211 if(md_ascii_eq(STR(off), str, len)) {
1212 /* Success. */
1213 *p_end = off + len;
1214 return TRUE;
1215 }
1216 off++;
1217 }
1218
1219 line_index++;
1220 if(off >= max_end || line_index >= n_lines) {
1221 /* Failure. */
1222 *p_scan_horizon = off;
1223 return FALSE;
1224 }
1225
1226 off = lines[line_index].beg;
1227 }
1228}
1229
1230static int
1231md_is_html_comment(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1232{
1233 OFF off = beg;
1234
1235 MD_ASSERT(CH(beg) == _T('<'));
1236
1237 if(off + 4 >= lines[0].end)
1238 return FALSE;
1239 if(CH(off+1) != _T('!') || CH(off+2) != _T('-') || CH(off+3) != _T('-'))
1240 return FALSE;
1241
1242 /* Skip only "<!" so that we accept also "<!-->" or "<!--->" */
1243 off += 2;
1244
1245 /* Scan for ordinary comment closer "-->". */
1246 return md_scan_for_html_closer(ctx, _T("-->"), 3,
1247 lines, n_lines, off, max_end, p_end, &ctx->html_comment_horizon);
1248}
1249
1250static int
1251md_is_html_processing_instruction(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1252{
1253 OFF off = beg;
1254
1255 if(off + 2 >= lines[0].end)
1256 return FALSE;
1257 if(CH(off+1) != _T('?'))
1258 return FALSE;
1259 off += 2;
1260
1261 return md_scan_for_html_closer(ctx, _T("?>"), 2,
1262 lines, n_lines, off, max_end, p_end, &ctx->html_proc_instr_horizon);
1263}
1264
1265static int
1266md_is_html_declaration(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1267{
1268 OFF off = beg;
1269
1270 if(off + 2 >= lines[0].end)
1271 return FALSE;
1272 if(CH(off+1) != _T('!'))
1273 return FALSE;
1274 off += 2;
1275
1276 /* Declaration name. */
1277 if(off >= lines[0].end || !ISALPHA(off))
1278 return FALSE;
1279 off++;
1280 while(off < lines[0].end && ISALPHA(off))
1281 off++;
1282
1283 return md_scan_for_html_closer(ctx, _T(">"), 1,
1284 lines, n_lines, off, max_end, p_end, &ctx->html_decl_horizon);
1285}
1286
1287static int
1288md_is_html_cdata(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1289{
1290 static const CHAR open_str[] = _T("<![CDATA[");
1291 static const SZ open_size = SIZEOF_ARRAY(open_str) - 1;
1292
1293 OFF off = beg;
1294
1295 if(off + open_size >= lines[0].end)
1296 return FALSE;
1297 if(memcmp(STR(off), open_str, open_size) != 0)
1298 return FALSE;
1299 off += open_size;
1300
1301 return md_scan_for_html_closer(ctx, _T("]]>"), 3,
1302 lines, n_lines, off, max_end, p_end, &ctx->html_cdata_horizon);
1303}
1304
1305static int
1306md_is_html_any(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1307{
1308 MD_ASSERT(CH(beg) == _T('<'));
1309 return (md_is_html_tag(ctx, lines, n_lines, beg, max_end, p_end) ||
1310 md_is_html_comment(ctx, lines, n_lines, beg, max_end, p_end) ||
1311 md_is_html_processing_instruction(ctx, lines, n_lines, beg, max_end, p_end) ||
1312 md_is_html_declaration(ctx, lines, n_lines, beg, max_end, p_end) ||
1313 md_is_html_cdata(ctx, lines, n_lines, beg, max_end, p_end));
1314}
1315
1316
1317/****************************
1318 *** Recognizing Entity ***
1319 ****************************/
1320
1321static int
1322md_is_hex_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
1323{
1324 OFF off = beg;
1325 MD_UNUSED(ctx);
1326
1327 while(off < max_end && ISXDIGIT_(text[off]) && off - beg <= 8)
1328 off++;
1329
1330 if(1 <= off - beg && off - beg <= 6) {
1331 *p_end = off;
1332 return TRUE;
1333 } else {
1334 return FALSE;
1335 }
1336}
1337
1338static int
1339md_is_dec_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
1340{
1341 OFF off = beg;
1342 MD_UNUSED(ctx);
1343
1344 while(off < max_end && ISDIGIT_(text[off]) && off - beg <= 8)
1345 off++;
1346
1347 if(1 <= off - beg && off - beg <= 7) {
1348 *p_end = off;
1349 return TRUE;
1350 } else {
1351 return FALSE;
1352 }
1353}
1354
1355static int
1356md_is_named_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
1357{
1358 OFF off = beg;
1359 MD_UNUSED(ctx);
1360
1361 if(off < max_end && ISALPHA_(text[off]))
1362 off++;
1363 else
1364 return FALSE;
1365
1366 while(off < max_end && ISALNUM_(text[off]) && off - beg <= 48)
1367 off++;
1368
1369 if(2 <= off - beg && off - beg <= 48) {
1370 *p_end = off;
1371 return TRUE;
1372 } else {
1373 return FALSE;
1374 }
1375}
1376
1377static int
1378md_is_entity_str(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
1379{
1380 int is_contents;
1381 OFF off = beg;
1382
1383 MD_ASSERT(text[off] == _T('&'));
1384 off++;
1385
1386 if(off+2 < max_end && text[off] == _T('#') && (text[off+1] == _T('x') || text[off+1] == _T('X')))
1387 is_contents = md_is_hex_entity_contents(ctx, text, off+2, max_end, &off);
1388 else if(off+1 < max_end && text[off] == _T('#'))
1389 is_contents = md_is_dec_entity_contents(ctx, text, off+1, max_end, &off);
1390 else
1391 is_contents = md_is_named_entity_contents(ctx, text, off, max_end, &off);
1392
1393 if(is_contents && off < max_end && text[off] == _T(';')) {
1394 *p_end = off+1;
1395 return TRUE;
1396 } else {
1397 return FALSE;
1398 }
1399}
1400
1401static inline int
1402md_is_entity(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end)
1403{
1404 return md_is_entity_str(ctx, ctx->text, beg, max_end, p_end);
1405}
1406
1407
1408/******************************
1409 *** Attribute Management ***
1410 ******************************/
1411
1412typedef struct MD_ATTRIBUTE_BUILD_tag MD_ATTRIBUTE_BUILD;
1413struct MD_ATTRIBUTE_BUILD_tag {
1414 CHAR* text;
1415 MD_TEXTTYPE* substr_types;
1416 OFF* substr_offsets;
1417 int substr_count;
1418 int substr_alloc;
1419 MD_TEXTTYPE trivial_types[1];
1420 OFF trivial_offsets[2];
1421};
1422
1423
1424#define MD_BUILD_ATTR_NO_ESCAPES 0x0001
1425
1426static int
1427md_build_attr_append_substr(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build,
1428 MD_TEXTTYPE type, OFF off)
1429{
1430 if(build->substr_count >= build->substr_alloc) {
1431 MD_TEXTTYPE* new_substr_types;
1432 OFF* new_substr_offsets;
1433
1434 build->substr_alloc = (build->substr_alloc > 0
1435 ? build->substr_alloc + build->substr_alloc / 2
1436 : 8);
1437 new_substr_types = (MD_TEXTTYPE*) realloc(build->substr_types,
1438 build->substr_alloc * sizeof(MD_TEXTTYPE));
1439 if(new_substr_types == NULL) {
1440 MD_LOG("realloc() failed.");
1441 return -1;
1442 }
1443 /* Note +1 to reserve space for final offset (== raw_size). */
1444 new_substr_offsets = (OFF*) realloc(build->substr_offsets,
1445 (build->substr_alloc+1) * sizeof(OFF));
1446 if(new_substr_offsets == NULL) {
1447 MD_LOG("realloc() failed.");
1448 free(new_substr_types);
1449 return -1;
1450 }
1451
1452 build->substr_types = new_substr_types;
1453 build->substr_offsets = new_substr_offsets;
1454 }
1455
1456 build->substr_types[build->substr_count] = type;
1457 build->substr_offsets[build->substr_count] = off;
1458 build->substr_count++;
1459 return 0;
1460}
1461
1462static void
1463md_free_attribute(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build)
1464{
1465 MD_UNUSED(ctx);
1466
1467 if(build->substr_alloc > 0) {
1468 free(build->text);
1469 free(build->substr_types);
1470 free(build->substr_offsets);
1471 }
1472}
1473
1474static int
1475md_build_attribute(MD_CTX* ctx, const CHAR* raw_text, SZ raw_size,
1476 unsigned flags, MD_ATTRIBUTE* attr, MD_ATTRIBUTE_BUILD* build)
1477{
1478 OFF raw_off, off;
1479 int is_trivial;
1480 int ret = 0;
1481
1482 memset(build, 0, sizeof(MD_ATTRIBUTE_BUILD));
1483
1484 /* If there is no backslash and no ampersand, build trivial attribute
1485 * without any malloc(). */
1486 is_trivial = TRUE;
1487 for(raw_off = 0; raw_off < raw_size; raw_off++) {
1488 if(ISANYOF3_(raw_text[raw_off], _T('\\'), _T('&'), _T('\0'))) {
1489 is_trivial = FALSE;
1490 break;
1491 }
1492 }
1493
1494 if(is_trivial) {
1495 build->text = (CHAR*) (raw_size ? raw_text : NULL);
1496 build->substr_types = build->trivial_types;
1497 build->substr_offsets = build->trivial_offsets;
1498 build->substr_count = 1;
1499 build->substr_alloc = 0;
1500 build->trivial_types[0] = MD_TEXT_NORMAL;
1501 build->trivial_offsets[0] = 0;
1502 build->trivial_offsets[1] = raw_size;
1503 off = raw_size;
1504 } else {
1505 build->text = (CHAR*) malloc(raw_size * sizeof(CHAR));
1506 if(build->text == NULL) {
1507 MD_LOG("malloc() failed.");
1508 goto abort;
1509 }
1510
1511 raw_off = 0;
1512 off = 0;
1513
1514 while(raw_off < raw_size) {
1515 if(raw_text[raw_off] == _T('\0')) {
1516 MD_CHECK(md_build_attr_append_substr(ctx, build, MD_TEXT_NULLCHAR, off));
1517 memcpy(build->text + off, raw_text + raw_off, 1);
1518 off++;
1519 raw_off++;
1520 continue;
1521 }
1522
1523 if(raw_text[raw_off] == _T('&')) {
1524 OFF ent_end;
1525
1526 if(md_is_entity_str(ctx, raw_text, raw_off, raw_size, &ent_end)) {
1527 MD_CHECK(md_build_attr_append_substr(ctx, build, MD_TEXT_ENTITY, off));
1528 memcpy(build->text + off, raw_text + raw_off, ent_end - raw_off);
1529 off += ent_end - raw_off;
1530 raw_off = ent_end;
1531 continue;
1532 }
1533 }
1534
1535 if(build->substr_count == 0 || build->substr_types[build->substr_count-1] != MD_TEXT_NORMAL)
1536 MD_CHECK(md_build_attr_append_substr(ctx, build, MD_TEXT_NORMAL, off));
1537
1538 if(!(flags & MD_BUILD_ATTR_NO_ESCAPES) &&
1539 raw_text[raw_off] == _T('\\') && raw_off+1 < raw_size &&
1540 (ISPUNCT_(raw_text[raw_off+1]) || ISNEWLINE_(raw_text[raw_off+1])))
1541 raw_off++;
1542
1543 build->text[off++] = raw_text[raw_off++];
1544 }
1545 build->substr_offsets[build->substr_count] = off;
1546 }
1547
1548 attr->text = build->text;
1549 attr->size = off;
1550 attr->substr_offsets = build->substr_offsets;
1551 attr->substr_types = build->substr_types;
1552 return 0;
1553
1554abort:
1555 md_free_attribute(ctx, build);
1556 return -1;
1557}
1558
1559
1560/*********************************************
1561 *** Dictionary of Reference Definitions ***
1562 *********************************************/
1563
1564#define MD_FNV1A_BASE 2166136261U
1565#define MD_FNV1A_PRIME 16777619U
1566
1567static inline unsigned
1568md_fnv1a(unsigned base, const void* data, size_t n)
1569{
1570 const unsigned char* buf = (const unsigned char*) data;
1571 unsigned hash = base;
1572 size_t i;
1573
1574 for(i = 0; i < n; i++) {
1575 hash ^= buf[i];
1576 hash *= MD_FNV1A_PRIME;
1577 }
1578
1579 return hash;
1580}
1581
1582
1583struct MD_REF_DEF_tag {
1584 CHAR* label;
1585 CHAR* title;
1586 unsigned hash;
1587 SZ label_size;
1588 SZ title_size;
1589 OFF dest_beg;
1590 OFF dest_end;
1591 unsigned char label_needs_free : 1;
1592 unsigned char title_needs_free : 1;
1593};
1594
1595/* Label equivalence is quite complicated with regards to whitespace and case
1596 * folding. This complicates computing a hash of it as well as direct comparison
1597 * of two labels. */
1598
1599static unsigned
1600md_link_label_hash(const CHAR* label, SZ size)
1601{
1602 unsigned hash = MD_FNV1A_BASE;
1603 OFF off;
1604 unsigned codepoint;
1605 int is_whitespace = FALSE;
1606
1607 off = md_skip_unicode_whitespace(label, 0, size);
1608 while(off < size) {
1609 SZ char_size;
1610
1611 codepoint = md_decode_unicode(label, off, size, &char_size);
1612 is_whitespace = ISUNICODEWHITESPACE_(codepoint) || ISNEWLINE_(label[off]);
1613
1614 if(is_whitespace) {
1615 codepoint = ' ';
1616 hash = md_fnv1a(hash, &codepoint, sizeof(unsigned));
1617 off = md_skip_unicode_whitespace(label, off, size);
1618 } else {
1619 MD_UNICODE_FOLD_INFO fold_info;
1620
1621 md_get_unicode_fold_info(codepoint, &fold_info);
1622 hash = md_fnv1a(hash, fold_info.codepoints, fold_info.n_codepoints * sizeof(unsigned));
1623 off += char_size;
1624 }
1625 }
1626
1627 return hash;
1628}
1629
1630static OFF
1631md_link_label_cmp_load_fold_info(const CHAR* label, OFF off, SZ size,
1632 MD_UNICODE_FOLD_INFO* fold_info)
1633{
1634 unsigned codepoint;
1635 SZ char_size;
1636
1637 if(off >= size) {
1638 /* Treat end of a link label as a whitespace. */
1639 goto whitespace;
1640 }
1641
1642 codepoint = md_decode_unicode(label, off, size, &char_size);
1643 off += char_size;
1644 if(ISUNICODEWHITESPACE_(codepoint)) {
1645 /* Treat all whitespace as equivalent */
1646 goto whitespace;
1647 }
1648
1649 /* Get real folding info. */
1650 md_get_unicode_fold_info(codepoint, fold_info);
1651 return off;
1652
1653whitespace:
1654 fold_info->codepoints[0] = _T(' ');
1655 fold_info->n_codepoints = 1;
1656 return md_skip_unicode_whitespace(label, off, size);
1657}
1658
1659static int
1660md_link_label_cmp(const CHAR* a_label, SZ a_size, const CHAR* b_label, SZ b_size)
1661{
1662 OFF a_off;
1663 OFF b_off;
1664 MD_UNICODE_FOLD_INFO a_fi = { { 0 }, 0 };
1665 MD_UNICODE_FOLD_INFO b_fi = { { 0 }, 0 };
1666 OFF a_fi_off = 0;
1667 OFF b_fi_off = 0;
1668 int cmp;
1669
1670 a_off = md_skip_unicode_whitespace(a_label, 0, a_size);
1671 b_off = md_skip_unicode_whitespace(b_label, 0, b_size);
1672 while(a_off < a_size || a_fi_off < a_fi.n_codepoints ||
1673 b_off < b_size || b_fi_off < b_fi.n_codepoints)
1674 {
1675 /* If needed, load fold info for next char. */
1676 if(a_fi_off >= a_fi.n_codepoints) {
1677 a_fi_off = 0;
1678 a_off = md_link_label_cmp_load_fold_info(a_label, a_off, a_size, &a_fi);
1679 }
1680 if(b_fi_off >= b_fi.n_codepoints) {
1681 b_fi_off = 0;
1682 b_off = md_link_label_cmp_load_fold_info(b_label, b_off, b_size, &b_fi);
1683 }
1684
1685 cmp = b_fi.codepoints[b_fi_off] - a_fi.codepoints[a_fi_off];
1686 if(cmp != 0)
1687 return cmp;
1688
1689 a_fi_off++;
1690 b_fi_off++;
1691 }
1692
1693 return 0;
1694}
1695
1696typedef struct MD_REF_DEF_LIST_tag MD_REF_DEF_LIST;
1697struct MD_REF_DEF_LIST_tag {
1698 int n_ref_defs;
1699 int alloc_ref_defs;
1700 MD_REF_DEF* ref_defs[]; /* Valid items always point into ctx->ref_defs[] */
1701};
1702
1703static int
1704md_ref_def_cmp(const void* a, const void* b)
1705{
1706 const MD_REF_DEF* a_ref = *(const MD_REF_DEF**)a;
1707 const MD_REF_DEF* b_ref = *(const MD_REF_DEF**)b;
1708
1709 if(a_ref->hash < b_ref->hash)
1710 return -1;
1711 else if(a_ref->hash > b_ref->hash)
1712 return +1;
1713 else
1714 return md_link_label_cmp(a_ref->label, a_ref->label_size, b_ref->label, b_ref->label_size);
1715}
1716
1717static int
1718md_ref_def_cmp_for_sort(const void* a, const void* b)
1719{
1720 int cmp;
1721
1722 cmp = md_ref_def_cmp(a, b);
1723
1724 /* Ensure stability of the sorting. */
1725 if(cmp == 0) {
1726 const MD_REF_DEF* a_ref = *(const MD_REF_DEF**)a;
1727 const MD_REF_DEF* b_ref = *(const MD_REF_DEF**)b;
1728
1729 if(a_ref < b_ref)
1730 cmp = -1;
1731 else if(a_ref > b_ref)
1732 cmp = +1;
1733 else
1734 cmp = 0;
1735 }
1736
1737 return cmp;
1738}
1739
1740static int
1741md_build_ref_def_hashtable(MD_CTX* ctx)
1742{
1743 int i, j;
1744
1745 if(ctx->n_ref_defs == 0)
1746 return 0;
1747
1748 ctx->ref_def_hashtable_size = (ctx->n_ref_defs * 5) / 4;
1749 ctx->ref_def_hashtable = malloc(ctx->ref_def_hashtable_size * sizeof(void*));
1750 if(ctx->ref_def_hashtable == NULL) {
1751 MD_LOG("malloc() failed.");
1752 goto abort;
1753 }
1754 memset(ctx->ref_def_hashtable, 0, ctx->ref_def_hashtable_size * sizeof(void*));
1755
1756 /* Each member of ctx->ref_def_hashtable[] can be:
1757 * -- NULL,
1758 * -- pointer to the MD_REF_DEF in ctx->ref_defs[], or
1759 * -- pointer to a MD_REF_DEF_LIST, which holds multiple pointers to
1760 * such MD_REF_DEFs.
1761 */
1762 for(i = 0; i < ctx->n_ref_defs; i++) {
1763 MD_REF_DEF* def = &ctx->ref_defs[i];
1764 void* bucket;
1765 MD_REF_DEF_LIST* list;
1766
1767 def->hash = md_link_label_hash(def->label, def->label_size);
1768 bucket = ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size];
1769
1770 if(bucket == NULL) {
1771 /* The bucket is empty. Make it just point to the def. */
1772 ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size] = def;
1773 continue;
1774 }
1775
1776 if(ctx->ref_defs <= (MD_REF_DEF*) bucket && (MD_REF_DEF*) bucket < ctx->ref_defs + ctx->n_ref_defs) {
1777 /* The bucket already contains one ref. def. Lets see whether it
1778 * is the same label (ref. def. duplicate) or different one
1779 * (hash conflict). */
1780 MD_REF_DEF* old_def = (MD_REF_DEF*) bucket;
1781
1782 if(md_link_label_cmp(def->label, def->label_size, old_def->label, old_def->label_size) == 0) {
1783 /* Duplicate label: Ignore this ref. def. */
1784 continue;
1785 }
1786
1787 /* Make the bucket complex, i.e. able to hold more ref. defs. */
1788 list = (MD_REF_DEF_LIST*) malloc(sizeof(MD_REF_DEF_LIST) + 2 * sizeof(MD_REF_DEF*));
1789 if(list == NULL) {
1790 MD_LOG("malloc() failed.");
1791 goto abort;
1792 }
1793 list->ref_defs[0] = old_def;
1794 list->ref_defs[1] = def;
1795 list->n_ref_defs = 2;
1796 list->alloc_ref_defs = 2;
1797 ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size] = list;
1798 continue;
1799 }
1800
1801 /* Append the def to the complex bucket list.
1802 *
1803 * Note in this case we ignore potential duplicates to avoid expensive
1804 * iterating over the complex bucket. Below, we revisit all the complex
1805 * buckets and handle it more cheaply after the complex bucket contents
1806 * is sorted. */
1807 list = (MD_REF_DEF_LIST*) bucket;
1808 if(list->n_ref_defs >= list->alloc_ref_defs) {
1809 int alloc_ref_defs = list->alloc_ref_defs + list->alloc_ref_defs / 2;
1810 MD_REF_DEF_LIST* list_tmp = (MD_REF_DEF_LIST*) realloc(list,
1811 sizeof(MD_REF_DEF_LIST) + alloc_ref_defs * sizeof(MD_REF_DEF*));
1812 if(list_tmp == NULL) {
1813 MD_LOG("realloc() failed.");
1814 goto abort;
1815 }
1816 list = list_tmp;
1817 list->alloc_ref_defs = alloc_ref_defs;
1818 ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size] = list;
1819 }
1820
1821 list->ref_defs[list->n_ref_defs] = def;
1822 list->n_ref_defs++;
1823 }
1824
1825 /* Sort the complex buckets so we can use bsearch() with them. */
1826 for(i = 0; i < ctx->ref_def_hashtable_size; i++) {
1827 void* bucket = ctx->ref_def_hashtable[i];
1828 MD_REF_DEF_LIST* list;
1829
1830 if(bucket == NULL)
1831 continue;
1832 if(ctx->ref_defs <= (MD_REF_DEF*) bucket && (MD_REF_DEF*) bucket < ctx->ref_defs + ctx->n_ref_defs)
1833 continue;
1834
1835 list = (MD_REF_DEF_LIST*) bucket;
1836 qsort(list->ref_defs, list->n_ref_defs, sizeof(MD_REF_DEF*), md_ref_def_cmp_for_sort);
1837
1838 /* Disable all duplicates in the complex bucket by forcing all such
1839 * records to point to the 1st such ref. def. I.e. no matter which
1840 * record is found during the lookup, it will always point to the right
1841 * ref. def. in ctx->ref_defs[]. */
1842 for(j = 1; j < list->n_ref_defs; j++) {
1843 if(md_ref_def_cmp(&list->ref_defs[j-1], &list->ref_defs[j]) == 0)
1844 list->ref_defs[j] = list->ref_defs[j-1];
1845 }
1846 }
1847
1848 return 0;
1849
1850abort:
1851 return -1;
1852}
1853
1854static void
1855md_free_ref_def_hashtable(MD_CTX* ctx)
1856{
1857 if(ctx->ref_def_hashtable != NULL) {
1858 int i;
1859
1860 for(i = 0; i < ctx->ref_def_hashtable_size; i++) {
1861 void* bucket = ctx->ref_def_hashtable[i];
1862 if(bucket == NULL)
1863 continue;
1864 if(ctx->ref_defs <= (MD_REF_DEF*) bucket && (MD_REF_DEF*) bucket < ctx->ref_defs + ctx->n_ref_defs)
1865 continue;
1866 free(bucket);
1867 }
1868
1869 free(ctx->ref_def_hashtable);
1870 }
1871}
1872
1873static const MD_REF_DEF*
1874md_lookup_ref_def(MD_CTX* ctx, const CHAR* label, SZ label_size)
1875{
1876 unsigned hash;
1877 void* bucket;
1878
1879 if(ctx->ref_def_hashtable_size == 0)
1880 return NULL;
1881
1882 hash = md_link_label_hash(label, label_size);
1883 bucket = ctx->ref_def_hashtable[hash % ctx->ref_def_hashtable_size];
1884
1885 if(bucket == NULL) {
1886 return NULL;
1887 } else if(ctx->ref_defs <= (MD_REF_DEF*) bucket && (MD_REF_DEF*) bucket < ctx->ref_defs + ctx->n_ref_defs) {
1888 const MD_REF_DEF* def = (MD_REF_DEF*) bucket;
1889
1890 if(md_link_label_cmp(def->label, def->label_size, label, label_size) == 0)
1891 return def;
1892 else
1893 return NULL;
1894 } else {
1895 MD_REF_DEF_LIST* list = (MD_REF_DEF_LIST*) bucket;
1896 MD_REF_DEF key_buf;
1897 const MD_REF_DEF* key = &key_buf;
1898 const MD_REF_DEF** ret;
1899
1900 key_buf.label = (CHAR*) label;
1901 key_buf.label_size = label_size;
1902 key_buf.hash = md_link_label_hash(key_buf.label, key_buf.label_size);
1903
1904 ret = (const MD_REF_DEF**) bsearch(&key, list->ref_defs,
1905 list->n_ref_defs, sizeof(MD_REF_DEF*), md_ref_def_cmp);
1906 if(ret != NULL)
1907 return *ret;
1908 else
1909 return NULL;
1910 }
1911}
1912
1913
1914/***************************
1915 *** Recognizing Links ***
1916 ***************************/
1917
1918/* Note this code is partially shared between processing inlines and blocks
1919 * as reference definitions and links share some helper parser functions.
1920 */
1921
1922typedef struct MD_LINK_ATTR_tag MD_LINK_ATTR;
1923struct MD_LINK_ATTR_tag {
1924 OFF dest_beg;
1925 OFF dest_end;
1926
1927 CHAR* title;
1928 SZ title_size;
1929 int title_needs_free;
1930};
1931
1932
1933static int
1934md_is_link_label(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg,
1935 OFF* p_end, MD_SIZE* p_beg_line_index, MD_SIZE* p_end_line_index,
1936 OFF* p_contents_beg, OFF* p_contents_end)
1937{
1938 OFF off = beg;
1939 OFF contents_beg = 0;
1940 OFF contents_end = 0;
1941 MD_SIZE line_index = 0;
1942 int len = 0;
1943
1944 *p_beg_line_index = 0;
1945
1946 if(CH(off) != _T('['))
1947 return FALSE;
1948 off++;
1949
1950 while(1) {
1951 OFF line_end = lines[line_index].end;
1952
1953 while(off < line_end) {
1954 if(CH(off) == _T('\\') && off+1 < ctx->size && (ISPUNCT(off+1) || ISNEWLINE(off+1))) {
1955 if(contents_end == 0) {
1956 contents_beg = off;
1957 *p_beg_line_index = line_index;
1958 }
1959 contents_end = off + 2;
1960 off += 2;
1961 } else if(CH(off) == _T('[')) {
1962 return FALSE;
1963 } else if(CH(off) == _T(']')) {
1964 if(contents_beg < contents_end) {
1965 /* Success. */
1966 *p_contents_beg = contents_beg;
1967 *p_contents_end = contents_end;
1968 *p_end = off+1;
1969 *p_end_line_index = line_index;
1970 return TRUE;
1971 } else {
1972 /* Link label must have some non-whitespace contents. */
1973 return FALSE;
1974 }
1975 } else {
1976 unsigned codepoint;
1977 SZ char_size;
1978
1979 codepoint = md_decode_unicode(ctx->text, off, ctx->size, &char_size);
1980 if(!ISUNICODEWHITESPACE_(codepoint)) {
1981 if(contents_end == 0) {
1982 contents_beg = off;
1983 *p_beg_line_index = line_index;
1984 }
1985 contents_end = off + char_size;
1986 }
1987
1988 off += char_size;
1989 }
1990
1991 len++;
1992 if(len > 999)
1993 return FALSE;
1994 }
1995
1996 line_index++;
1997 len++;
1998 if(line_index < n_lines)
1999 off = lines[line_index].beg;
2000 else
2001 break;
2002 }
2003
2004 return FALSE;
2005}
2006
2007static int
2008md_is_link_destination_A(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end,
2009 OFF* p_contents_beg, OFF* p_contents_end)
2010{
2011 OFF off = beg;
2012
2013 if(off >= max_end || CH(off) != _T('<'))
2014 return FALSE;
2015 off++;
2016
2017 while(off < max_end) {
2018 if(CH(off) == _T('\\') && off+1 < max_end && ISPUNCT(off+1)) {
2019 off += 2;
2020 continue;
2021 }
2022
2023 if(ISNEWLINE(off) || CH(off) == _T('<'))
2024 return FALSE;
2025
2026 if(CH(off) == _T('>')) {
2027 /* Success. */
2028 *p_contents_beg = beg+1;
2029 *p_contents_end = off;
2030 *p_end = off+1;
2031 return TRUE;
2032 }
2033
2034 off++;
2035 }
2036
2037 return FALSE;
2038}
2039
2040static int
2041md_is_link_destination_B(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end,
2042 OFF* p_contents_beg, OFF* p_contents_end)
2043{
2044 OFF off = beg;
2045 int parenthesis_level = 0;
2046
2047 while(off < max_end) {
2048 if(CH(off) == _T('\\') && off+1 < max_end && ISPUNCT(off+1)) {
2049 off += 2;
2050 continue;
2051 }
2052
2053 if(ISWHITESPACE(off) || ISCNTRL(off))
2054 break;
2055
2056 /* Link destination may include balanced pairs of unescaped '(' ')'.
2057 * Note we limit the maximal nesting level by 32 to protect us from
2058 * https://github.com/jgm/cmark/issues/214 */
2059 if(CH(off) == _T('(')) {
2060 parenthesis_level++;
2061 if(parenthesis_level > 32)
2062 return FALSE;
2063 } else if(CH(off) == _T(')')) {
2064 if(parenthesis_level == 0)
2065 break;
2066 parenthesis_level--;
2067 }
2068
2069 off++;
2070 }
2071
2072 if(parenthesis_level != 0 || off == beg)
2073 return FALSE;
2074
2075 /* Success. */
2076 *p_contents_beg = beg;
2077 *p_contents_end = off;
2078 *p_end = off;
2079 return TRUE;
2080}
2081
2082static inline int
2083md_is_link_destination(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end,
2084 OFF* p_contents_beg, OFF* p_contents_end)
2085{
2086 if(CH(beg) == _T('<'))
2087 return md_is_link_destination_A(ctx, beg, max_end, p_end, p_contents_beg, p_contents_end);
2088 else
2089 return md_is_link_destination_B(ctx, beg, max_end, p_end, p_contents_beg, p_contents_end);
2090}
2091
2092static int
2093md_is_link_title(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg,
2094 OFF* p_end, MD_SIZE* p_beg_line_index, MD_SIZE* p_end_line_index,
2095 OFF* p_contents_beg, OFF* p_contents_end)
2096{
2097 OFF off = beg;
2098 CHAR closer_char;
2099 MD_SIZE line_index = 0;
2100
2101 /* White space with up to one line break. */
2102 while(off < lines[line_index].end && ISWHITESPACE(off))
2103 off++;
2104 if(off >= lines[line_index].end) {
2105 line_index++;
2106 if(line_index >= n_lines)
2107 return FALSE;
2108 off = lines[line_index].beg;
2109 }
2110 if(off == beg)
2111 return FALSE;
2112
2113 *p_beg_line_index = line_index;
2114
2115 /* First char determines how to detect end of it. */
2116 switch(CH(off)) {
2117 case _T('"'): closer_char = _T('"'); break;
2118 case _T('\''): closer_char = _T('\''); break;
2119 case _T('('): closer_char = _T(')'); break;
2120 default: return FALSE;
2121 }
2122 off++;
2123
2124 *p_contents_beg = off;
2125
2126 while(line_index < n_lines) {
2127 OFF line_end = lines[line_index].end;
2128
2129 while(off < line_end) {
2130 if(CH(off) == _T('\\') && off+1 < ctx->size && (ISPUNCT(off+1) || ISNEWLINE(off+1))) {
2131 off++;
2132 } else if(CH(off) == closer_char) {
2133 /* Success. */
2134 *p_contents_end = off;
2135 *p_end = off+1;
2136 *p_end_line_index = line_index;
2137 return TRUE;
2138 } else if(closer_char == _T(')') && CH(off) == _T('(')) {
2139 /* ()-style title cannot contain (unescaped '(')) */
2140 return FALSE;
2141 }
2142
2143 off++;
2144 }
2145
2146 line_index++;
2147 }
2148
2149 return FALSE;
2150}
2151
2152/* Returns 0 if it is not a reference definition.
2153 *
2154 * Returns N > 0 if it is a reference definition. N then corresponds to the
2155 * number of lines forming it). In this case the definition is stored for
2156 * resolving any links referring to it.
2157 *
2158 * Returns -1 in case of an error (out of memory).
2159 */
2160static int
2161md_is_link_reference_definition(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines)
2162{
2163 OFF label_contents_beg;
2164 OFF label_contents_end;
2165 MD_SIZE label_contents_line_index;
2166 int label_is_multiline = FALSE;
2167 OFF dest_contents_beg;
2168 OFF dest_contents_end;
2169 OFF title_contents_beg;
2170 OFF title_contents_end;
2171 MD_SIZE title_contents_line_index;
2172 int title_is_multiline = FALSE;
2173 OFF off;
2174 MD_SIZE line_index = 0;
2175 MD_SIZE tmp_line_index;
2176 MD_REF_DEF* def = NULL;
2177 int ret = 0;
2178
2179 /* Link label. */
2180 if(!md_is_link_label(ctx, lines, n_lines, lines[0].beg,
2181 &off, &label_contents_line_index, &line_index,
2182 &label_contents_beg, &label_contents_end))
2183 return FALSE;
2184 label_is_multiline = (label_contents_line_index != line_index);
2185
2186 /* Colon. */
2187 if(off >= lines[line_index].end || CH(off) != _T(':'))
2188 return FALSE;
2189 off++;
2190
2191 /* Optional white space with up to one line break. */
2192 while(off < lines[line_index].end && ISWHITESPACE(off))
2193 off++;
2194 if(off >= lines[line_index].end) {
2195 line_index++;
2196 if(line_index >= n_lines)
2197 return FALSE;
2198 off = lines[line_index].beg;
2199 }
2200
2201 /* Link destination. */
2202 if(!md_is_link_destination(ctx, off, lines[line_index].end,
2203 &off, &dest_contents_beg, &dest_contents_end))
2204 return FALSE;
2205
2206 /* (Optional) title. Note we interpret it as an title only if nothing
2207 * more follows on its last line. */
2208 if(md_is_link_title(ctx, lines + line_index, n_lines - line_index, off,
2209 &off, &title_contents_line_index, &tmp_line_index,
2210 &title_contents_beg, &title_contents_end)
2211 && off >= lines[line_index + tmp_line_index].end)
2212 {
2213 title_is_multiline = (tmp_line_index != title_contents_line_index);
2214 title_contents_line_index += line_index;
2215 line_index += tmp_line_index;
2216 } else {
2217 /* Not a title. */
2218 title_is_multiline = FALSE;
2219 title_contents_beg = off;
2220 title_contents_end = off;
2221 title_contents_line_index = 0;
2222 }
2223
2224 /* Nothing more can follow on the last line. */
2225 if(off < lines[line_index].end)
2226 return FALSE;
2227
2228 /* So, it _is_ a reference definition. Remember it. */
2229 if(ctx->n_ref_defs >= ctx->alloc_ref_defs) {
2230 MD_REF_DEF* new_defs;
2231
2232 ctx->alloc_ref_defs = (ctx->alloc_ref_defs > 0
2233 ? ctx->alloc_ref_defs + ctx->alloc_ref_defs / 2
2234 : 16);
2235 new_defs = (MD_REF_DEF*) realloc(ctx->ref_defs, ctx->alloc_ref_defs * sizeof(MD_REF_DEF));
2236 if(new_defs == NULL) {
2237 MD_LOG("realloc() failed.");
2238 goto abort;
2239 }
2240
2241 ctx->ref_defs = new_defs;
2242 }
2243 def = &ctx->ref_defs[ctx->n_ref_defs];
2244 memset(def, 0, sizeof(MD_REF_DEF));
2245
2246 if(label_is_multiline) {
2247 MD_CHECK(md_merge_lines_alloc(ctx, label_contents_beg, label_contents_end,
2248 lines + label_contents_line_index, n_lines - label_contents_line_index,
2249 _T(' '), &def->label, &def->label_size));
2250 def->label_needs_free = TRUE;
2251 } else {
2252 def->label = (CHAR*) STR(label_contents_beg);
2253 def->label_size = label_contents_end - label_contents_beg;
2254 }
2255
2256 if(title_is_multiline) {
2257 MD_CHECK(md_merge_lines_alloc(ctx, title_contents_beg, title_contents_end,
2258 lines + title_contents_line_index, n_lines - title_contents_line_index,
2259 _T('\n'), &def->title, &def->title_size));
2260 def->title_needs_free = TRUE;
2261 } else {
2262 def->title = (CHAR*) STR(title_contents_beg);
2263 def->title_size = title_contents_end - title_contents_beg;
2264 }
2265
2266 def->dest_beg = dest_contents_beg;
2267 def->dest_end = dest_contents_end;
2268
2269 /* Success. */
2270 ctx->n_ref_defs++;
2271 return line_index + 1;
2272
2273abort:
2274 /* Failure. */
2275 if(def != NULL && def->label_needs_free)
2276 free(def->label);
2277 if(def != NULL && def->title_needs_free)
2278 free(def->title);
2279 return ret;
2280}
2281
2282static int
2283md_is_link_reference(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
2284 OFF beg, OFF end, MD_LINK_ATTR* attr)
2285{
2286 const MD_REF_DEF* def;
2287 const MD_LINE* beg_line;
2288 int is_multiline;
2289 CHAR* label;
2290 SZ label_size;
2291 int ret = FALSE;
2292
2293 MD_ASSERT(CH(beg) == _T('[') || CH(beg) == _T('!'));
2294 MD_ASSERT(CH(end-1) == _T(']'));
2295
2296 if(ctx->max_ref_def_output == 0)
2297 return FALSE;
2298
2299 beg += (CH(beg) == _T('!') ? 2 : 1);
2300 end--;
2301
2302 /* Find lines corresponding to the beg and end positions. */
2303 beg_line = md_lookup_line(beg, lines, n_lines, NULL);
2304 is_multiline = (end > beg_line->end);
2305
2306 if(is_multiline) {
2307 MD_CHECK(md_merge_lines_alloc(ctx, beg, end, beg_line,
2308 (int)(n_lines - (beg_line - lines)), _T(' '), &label, &label_size));
2309 } else {
2310 label = (CHAR*) STR(beg);
2311 label_size = end - beg;
2312 }
2313
2314 def = md_lookup_ref_def(ctx, label, label_size);
2315 if(def != NULL) {
2316 attr->dest_beg = def->dest_beg;
2317 attr->dest_end = def->dest_end;
2318 attr->title = def->title;
2319 attr->title_size = def->title_size;
2320 attr->title_needs_free = FALSE;
2321 }
2322
2323 if(is_multiline)
2324 free(label);
2325
2326 if(def != NULL) {
2327 /* See https://github.com/mity/md4c/issues/238 */
2328 MD_SIZE output_size_estimation = def->label_size + def->title_size + def->dest_end - def->dest_beg;
2329 if(output_size_estimation < ctx->max_ref_def_output) {
2330 ctx->max_ref_def_output -= output_size_estimation;
2331 ret = TRUE;
2332 } else {
2333 MD_LOG("Too many link reference definition instantiations.");
2334 ctx->max_ref_def_output = 0;
2335 }
2336 }
2337
2338abort:
2339 return ret;
2340}
2341
2342static int
2343md_is_inline_link_spec(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
2344 OFF beg, OFF* p_end, MD_LINK_ATTR* attr)
2345{
2346 MD_SIZE line_index = 0;
2347 MD_SIZE tmp_line_index;
2348 OFF title_contents_beg;
2349 OFF title_contents_end;
2350 MD_SIZE title_contents_line_index;
2351 int title_is_multiline;
2352 OFF off = beg;
2353 int ret = FALSE;
2354
2355 md_lookup_line(off, lines, n_lines, &line_index);
2356
2357 MD_ASSERT(CH(off) == _T('('));
2358 off++;
2359
2360 /* Optional white space with up to one line break. */
2361 while(off < lines[line_index].end && ISWHITESPACE(off))
2362 off++;
2363 if(off >= lines[line_index].end && (off >= ctx->size || ISNEWLINE(off))) {
2364 line_index++;
2365 if(line_index >= n_lines)
2366 return FALSE;
2367 off = lines[line_index].beg;
2368 }
2369
2370 /* Link destination may be omitted, but only when not also having a title. */
2371 if(off < ctx->size && CH(off) == _T(')')) {
2372 attr->dest_beg = off;
2373 attr->dest_end = off;
2374 attr->title = NULL;
2375 attr->title_size = 0;
2376 attr->title_needs_free = FALSE;
2377 off++;
2378 *p_end = off;
2379 return TRUE;
2380 }
2381
2382 /* Link destination. */
2383 if(!md_is_link_destination(ctx, off, lines[line_index].end,
2384 &off, &attr->dest_beg, &attr->dest_end))
2385 return FALSE;
2386
2387 /* (Optional) title. */
2388 if(md_is_link_title(ctx, lines + line_index, n_lines - line_index, off,
2389 &off, &title_contents_line_index, &tmp_line_index,
2390 &title_contents_beg, &title_contents_end))
2391 {
2392 title_is_multiline = (tmp_line_index != title_contents_line_index);
2393 title_contents_line_index += line_index;
2394 line_index += tmp_line_index;
2395 } else {
2396 /* Not a title. */
2397 title_is_multiline = FALSE;
2398 title_contents_beg = off;
2399 title_contents_end = off;
2400 title_contents_line_index = 0;
2401 }
2402
2403 /* Optional whitespace followed with final ')'. */
2404 while(off < lines[line_index].end && ISWHITESPACE(off))
2405 off++;
2406 if(off >= lines[line_index].end) {
2407 line_index++;
2408 if(line_index >= n_lines)
2409 return FALSE;
2410 off = lines[line_index].beg;
2411 }
2412 if(CH(off) != _T(')'))
2413 goto abort;
2414 off++;
2415
2416 if(title_contents_beg >= title_contents_end) {
2417 attr->title = NULL;
2418 attr->title_size = 0;
2419 attr->title_needs_free = FALSE;
2420 } else if(!title_is_multiline) {
2421 attr->title = (CHAR*) STR(title_contents_beg);
2422 attr->title_size = title_contents_end - title_contents_beg;
2423 attr->title_needs_free = FALSE;
2424 } else {
2425 MD_CHECK(md_merge_lines_alloc(ctx, title_contents_beg, title_contents_end,
2426 lines + title_contents_line_index, n_lines - title_contents_line_index,
2427 _T('\n'), &attr->title, &attr->title_size));
2428 attr->title_needs_free = TRUE;
2429 }
2430
2431 *p_end = off;
2432 ret = TRUE;
2433
2434abort:
2435 return ret;
2436}
2437
2438static void
2439md_free_ref_defs(MD_CTX* ctx)
2440{
2441 int i;
2442
2443 for(i = 0; i < ctx->n_ref_defs; i++) {
2444 MD_REF_DEF* def = &ctx->ref_defs[i];
2445
2446 if(def->label_needs_free)
2447 free(def->label);
2448 if(def->title_needs_free)
2449 free(def->title);
2450 }
2451
2452 free(ctx->ref_defs);
2453}
2454
2455
2456/******************************************
2457 *** Processing Inlines (a.k.a Spans) ***
2458 ******************************************/
2459
2460/* We process inlines in few phases:
2461 *
2462 * (1) We go through the block text and collect all significant characters
2463 * which may start/end a span or some other significant position into
2464 * ctx->marks[]. Core of this is what md_collect_marks() does.
2465 *
2466 * We also do some very brief preliminary context-less analysis, whether
2467 * it might be opener or closer (e.g. of an emphasis span).
2468 *
2469 * This speeds the other steps as we do not need to re-iterate over all
2470 * characters anymore.
2471 *
2472 * (2) We analyze each potential mark types, in order by their precedence.
2473 *
2474 * In each md_analyze_XXX() function, we re-iterate list of the marks,
2475 * skipping already resolved regions (in preceding precedences) and try to
2476 * resolve them.
2477 *
2478 * (2.1) For trivial marks, which are single (e.g. HTML entity), we just mark
2479 * them as resolved.
2480 *
2481 * (2.2) For range-type marks, we analyze whether the mark could be closer
2482 * and, if yes, whether there is some preceding opener it could satisfy.
2483 *
2484 * If not we check whether it could be really an opener and if yes, we
2485 * remember it so subsequent closers may resolve it.
2486 *
2487 * (3) Finally, when all marks were analyzed, we render the block contents
2488 * by calling MD_RENDERER::text() callback, interrupting by ::enter_span()
2489 * or ::close_span() whenever we reach a resolved mark.
2490 */
2491
2492
2493/* The mark structure.
2494 *
2495 * '\\': Maybe escape sequence.
2496 * '\0': NULL char.
2497 * '*': Maybe (strong) emphasis start/end.
2498 * '_': Maybe (strong) emphasis start/end.
2499 * '~': Maybe strikethrough start/end (needs MD_FLAG_STRIKETHROUGH).
2500 * '`': Maybe code span start/end.
2501 * '&': Maybe start of entity.
2502 * ';': Maybe end of entity.
2503 * '<': Maybe start of raw HTML or autolink.
2504 * '>': Maybe end of raw HTML or autolink.
2505 * '[': Maybe start of link label or link text.
2506 * '!': Equivalent of '[' for image.
2507 * ']': Maybe end of link label or link text.
2508 * '@': Maybe permissive e-mail auto-link (needs MD_FLAG_PERMISSIVEEMAILAUTOLINKS).
2509 * ':': Maybe permissive URL auto-link (needs MD_FLAG_PERMISSIVEURLAUTOLINKS).
2510 * '.': Maybe permissive WWW auto-link (needs MD_FLAG_PERMISSIVEWWWAUTOLINKS).
2511 * 'D': Dummy mark, it reserves a space for splitting a previous mark
2512 * (e.g. emphasis) or to make more space for storing some special data
2513 * related to the preceding mark (e.g. link).
2514 *
2515 * Note that not all instances of these chars in the text imply creation of the
2516 * structure. Only those which have (or may have, after we see more context)
2517 * the special meaning.
2518 *
2519 * (Keep this struct as small as possible to fit as much of them into CPU
2520 * cache line.)
2521 */
2522struct MD_MARK_tag {
2523 OFF beg;
2524 OFF end;
2525
2526 /* For unresolved openers, 'next' may be used to form a stack of
2527 * unresolved open openers.
2528 *
2529 * When resolved with MD_MARK_OPENER/CLOSER flag, next/prev is index of the
2530 * respective closer/opener.
2531 */
2532 int prev;
2533 int next;
2534 CHAR ch;
2535 unsigned char flags;
2536};
2537
2538/* Mark flags (these apply to ALL mark types). */
2539#define MD_MARK_POTENTIAL_OPENER 0x01 /* Maybe opener. */
2540#define MD_MARK_POTENTIAL_CLOSER 0x02 /* Maybe closer. */
2541#define MD_MARK_OPENER 0x04 /* Definitely opener. */
2542#define MD_MARK_CLOSER 0x08 /* Definitely closer. */
2543#define MD_MARK_RESOLVED 0x10 /* Resolved in any definite way. */
2544
2545/* Mark flags specific for various mark types (so they can share bits). */
2546#define MD_MARK_EMPH_OC 0x20 /* Opener/closer mixed candidate. Helper for the "rule of 3". */
2547#define MD_MARK_EMPH_MOD3_0 0x40
2548#define MD_MARK_EMPH_MOD3_1 0x80
2549#define MD_MARK_EMPH_MOD3_2 (0x40 | 0x80)
2550#define MD_MARK_EMPH_MOD3_MASK (0x40 | 0x80)
2551#define MD_MARK_AUTOLINK 0x20 /* Distinguisher for '<', '>'. */
2552#define MD_MARK_AUTOLINK_MISSING_MAILTO 0x40
2553#define MD_MARK_VALIDPERMISSIVEAUTOLINK 0x20 /* For permissive autolinks. */
2554#define MD_MARK_HASNESTEDBRACKETS 0x20 /* For '[' to rule out invalid link labels early */
2555
2556static MD_MARKSTACK*
2557md_emph_stack(MD_CTX* ctx, MD_CHAR ch, unsigned flags)
2558{
2559 MD_MARKSTACK* stack;
2560
2561 switch(ch) {
2562 case '*': stack = &ASTERISK_OPENERS_oo_mod3_0; break;
2563 case '_': stack = &UNDERSCORE_OPENERS_oo_mod3_0; break;
2564 default: MD_UNREACHABLE();
2565 }
2566
2567 if(flags & MD_MARK_EMPH_OC)
2568 stack += 3;
2569
2570 switch(flags & MD_MARK_EMPH_MOD3_MASK) {
2571 case MD_MARK_EMPH_MOD3_0: stack += 0; break;
2572 case MD_MARK_EMPH_MOD3_1: stack += 1; break;
2573 case MD_MARK_EMPH_MOD3_2: stack += 2; break;
2574 default: MD_UNREACHABLE();
2575 }
2576
2577 return stack;
2578}
2579
2580static MD_MARKSTACK*
2581md_opener_stack(MD_CTX* ctx, int mark_index)
2582{
2583 MD_MARK* mark = &ctx->marks[mark_index];
2584
2585 switch(mark->ch) {
2586 case _T('*'):
2587 case _T('_'): return md_emph_stack(ctx, mark->ch, mark->flags);
2588
2589 case _T('~'): return (mark->end - mark->beg == 1) ? &TILDE_OPENERS_1 : &TILDE_OPENERS_2;
2590
2591 case _T('!'):
2592 case _T('['): return &BRACKET_OPENERS;
2593
2594 default: MD_UNREACHABLE();
2595 }
2596}
2597
2598static MD_MARK*
2599md_add_mark(MD_CTX* ctx)
2600{
2601 if(ctx->n_marks >= ctx->alloc_marks) {
2602 MD_MARK* new_marks;
2603
2604 ctx->alloc_marks = (ctx->alloc_marks > 0
2605 ? ctx->alloc_marks + ctx->alloc_marks / 2
2606 : 64);
2607 new_marks = realloc(ctx->marks, ctx->alloc_marks * sizeof(MD_MARK));
2608 if(new_marks == NULL) {
2609 MD_LOG("realloc() failed.");
2610 return NULL;
2611 }
2612
2613 ctx->marks = new_marks;
2614 }
2615
2616 return &ctx->marks[ctx->n_marks++];
2617}
2618
2619#define ADD_MARK_() \
2620 do { \
2621 mark = md_add_mark(ctx); \
2622 if(mark == NULL) { \
2623 ret = -1; \
2624 goto abort; \
2625 } \
2626 } while(0)
2627
2628#define ADD_MARK(ch_, beg_, end_, flags_) \
2629 do { \
2630 ADD_MARK_(); \
2631 mark->beg = (beg_); \
2632 mark->end = (end_); \
2633 mark->prev = -1; \
2634 mark->next = -1; \
2635 mark->ch = (char)(ch_); \
2636 mark->flags = (flags_); \
2637 } while(0)
2638
2639
2640static inline void
2641md_mark_stack_push(MD_CTX* ctx, MD_MARKSTACK* stack, int mark_index)
2642{
2643 ctx->marks[mark_index].next = stack->top;
2644 stack->top = mark_index;
2645}
2646
2647static inline int
2648md_mark_stack_pop(MD_CTX* ctx, MD_MARKSTACK* stack)
2649{
2650 int top = stack->top;
2651 if(top >= 0)
2652 stack->top = ctx->marks[top].next;
2653 return top;
2654}
2655
2656/* Sometimes, we need to store a pointer into the mark. It is quite rare
2657 * so we do not bother to make MD_MARK use union, and it can only happen
2658 * for dummy marks. */
2659static inline void
2660md_mark_store_ptr(MD_CTX* ctx, int mark_index, void* ptr)
2661{
2662 MD_MARK* mark = &ctx->marks[mark_index];
2663 MD_ASSERT(mark->ch == 'D');
2664
2665 /* Check only members beg and end are misused for this. */
2666 MD_ASSERT(sizeof(void*) <= 2 * sizeof(OFF));
2667 memcpy(mark, &ptr, sizeof(void*));
2668}
2669
2670static inline void*
2671md_mark_get_ptr(MD_CTX* ctx, int mark_index)
2672{
2673 void* ptr;
2674 MD_MARK* mark = &ctx->marks[mark_index];
2675 MD_ASSERT(mark->ch == 'D');
2676 memcpy(&ptr, mark, sizeof(void*));
2677 return ptr;
2678}
2679
2680static inline void
2681md_resolve_range(MD_CTX* ctx, int opener_index, int closer_index)
2682{
2683 MD_MARK* opener = &ctx->marks[opener_index];
2684 MD_MARK* closer = &ctx->marks[closer_index];
2685
2686 /* Interconnect opener and closer and mark both as resolved. */
2687 opener->next = closer_index;
2688 closer->prev = opener_index;
2689
2690 opener->flags |= MD_MARK_OPENER | MD_MARK_RESOLVED;
2691 closer->flags |= MD_MARK_CLOSER | MD_MARK_RESOLVED;
2692}
2693
2694
2695#define MD_ROLLBACK_CROSSING 0
2696#define MD_ROLLBACK_ALL 1
2697
2698/* In the range ctx->marks[opener_index] ... [closer_index], undo some or all
2699 * resolvings accordingly to these rules:
2700 *
2701 * (1) All stacks of openers are cut so that any pending potential openers
2702 * are discarded from future consideration.
2703 *
2704 * (2) If 'how' is MD_ROLLBACK_ALL, then ALL resolved marks inside the range
2705 * are thrown away and turned into dummy marks ('D').
2706 *
2707 * WARNING: Do not call for arbitrary range of opener and closer.
2708 * This must form (potentially) valid range not crossing nesting boundaries
2709 * of already resolved ranges.
2710 */
2711static void
2712md_rollback(MD_CTX* ctx, int opener_index, int closer_index, int how)
2713{
2714 int i;
2715
2716 for(i = 0; i < (int) SIZEOF_ARRAY(ctx->opener_stacks); i++) {
2717 MD_MARKSTACK* stack = &ctx->opener_stacks[i];
2718 while(stack->top >= opener_index)
2719 md_mark_stack_pop(ctx, stack);
2720 }
2721
2722 if(how == MD_ROLLBACK_ALL) {
2723 for(i = opener_index + 1; i < closer_index; i++) {
2724 ctx->marks[i].ch = 'D';
2725 ctx->marks[i].flags = 0;
2726 }
2727 }
2728}
2729
2730static void
2731md_build_mark_char_map(MD_CTX* ctx)
2732{
2733 memset(ctx->mark_char_map, 0, sizeof(ctx->mark_char_map));
2734
2735 ctx->mark_char_map['\\'] = 1;
2736 ctx->mark_char_map['*'] = 1;
2737 ctx->mark_char_map['_'] = 1;
2738 ctx->mark_char_map['`'] = 1;
2739 ctx->mark_char_map['&'] = 1;
2740 ctx->mark_char_map[';'] = 1;
2741 ctx->mark_char_map['<'] = 1;
2742 ctx->mark_char_map['>'] = 1;
2743 ctx->mark_char_map['['] = 1;
2744 ctx->mark_char_map['!'] = 1;
2745 ctx->mark_char_map[']'] = 1;
2746 ctx->mark_char_map['\0'] = 1;
2747
2748 if(ctx->parser.flags & MD_FLAG_STRIKETHROUGH)
2749 ctx->mark_char_map['~'] = 1;
2750
2751 if(ctx->parser.flags & MD_FLAG_LATEXMATHSPANS)
2752 ctx->mark_char_map['$'] = 1;
2753
2754 if(ctx->parser.flags & MD_FLAG_PERMISSIVEEMAILAUTOLINKS)
2755 ctx->mark_char_map['@'] = 1;
2756
2757 if(ctx->parser.flags & MD_FLAG_PERMISSIVEURLAUTOLINKS)
2758 ctx->mark_char_map[':'] = 1;
2759
2760 if(ctx->parser.flags & MD_FLAG_PERMISSIVEWWWAUTOLINKS)
2761 ctx->mark_char_map['.'] = 1;
2762
2763 if((ctx->parser.flags & MD_FLAG_TABLES) || (ctx->parser.flags & MD_FLAG_WIKILINKS))
2764 ctx->mark_char_map['|'] = 1;
2765
2766 if(ctx->parser.flags & MD_FLAG_COLLAPSEWHITESPACE) {
2767 int i;
2768
2769 for(i = 0; i < (int) sizeof(ctx->mark_char_map); i++) {
2770 if(ISWHITESPACE_(i))
2771 ctx->mark_char_map[i] = 1;
2772 }
2773 }
2774}
2775
2776static int
2777md_is_code_span(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg,
2778 MD_MARK* opener, MD_MARK* closer,
2779 OFF last_potential_closers[CODESPAN_MARK_MAXLEN],
2780 int* p_reached_paragraph_end)
2781{
2782 OFF opener_beg = beg;
2783 OFF opener_end;
2784 OFF closer_beg;
2785 OFF closer_end;
2786 SZ mark_len;
2787 OFF line_end;
2788 int has_space_after_opener = FALSE;
2789 int has_eol_after_opener = FALSE;
2790 int has_space_before_closer = FALSE;
2791 int has_eol_before_closer = FALSE;
2792 int has_only_space = TRUE;
2793 MD_SIZE line_index = 0;
2794
2795 line_end = lines[0].end;
2796 opener_end = opener_beg;
2797 while(opener_end < line_end && CH(opener_end) == _T('`'))
2798 opener_end++;
2799 has_space_after_opener = (opener_end < line_end && CH(opener_end) == _T(' '));
2800 has_eol_after_opener = (opener_end == line_end);
2801
2802 /* The caller needs to know end of the opening mark even if we fail. */
2803 opener->end = opener_end;
2804
2805 mark_len = opener_end - opener_beg;
2806 if(mark_len > CODESPAN_MARK_MAXLEN)
2807 return FALSE;
2808
2809 /* Check whether we already know there is no closer of this length.
2810 * If so, re-scan does no sense. This fixes issue #59. */
2811 if(last_potential_closers[mark_len-1] >= lines[n_lines-1].end ||
2812 (*p_reached_paragraph_end && last_potential_closers[mark_len-1] < opener_end))
2813 return FALSE;
2814
2815 closer_beg = opener_end;
2816 closer_end = opener_end;
2817
2818 /* Find closer mark. */
2819 while(TRUE) {
2820 while(closer_beg < line_end && CH(closer_beg) != _T('`')) {
2821 if(CH(closer_beg) != _T(' '))
2822 has_only_space = FALSE;
2823 closer_beg++;
2824 }
2825 closer_end = closer_beg;
2826 while(closer_end < line_end && CH(closer_end) == _T('`'))
2827 closer_end++;
2828
2829 if(closer_end - closer_beg == mark_len) {
2830 /* Success. */
2831 has_space_before_closer = (closer_beg > lines[line_index].beg && CH(closer_beg-1) == _T(' '));
2832 has_eol_before_closer = (closer_beg == lines[line_index].beg);
2833 break;
2834 }
2835
2836 if(closer_end - closer_beg > 0) {
2837 /* We have found a back-tick which is not part of the closer. */
2838 has_only_space = FALSE;
2839
2840 /* But if we eventually fail, remember it as a potential closer
2841 * of its own length for future attempts. This mitigates needs for
2842 * rescans. */
2843 if(closer_end - closer_beg < CODESPAN_MARK_MAXLEN) {
2844 if(closer_beg > last_potential_closers[closer_end - closer_beg - 1])
2845 last_potential_closers[closer_end - closer_beg - 1] = closer_beg;
2846 }
2847 }
2848
2849 if(closer_end >= line_end) {
2850 line_index++;
2851 if(line_index >= n_lines) {
2852 /* Reached end of the paragraph and still nothing. */
2853 *p_reached_paragraph_end = TRUE;
2854 return FALSE;
2855 }
2856 /* Try on the next line. */
2857 line_end = lines[line_index].end;
2858 closer_beg = lines[line_index].beg;
2859 } else {
2860 closer_beg = closer_end;
2861 }
2862 }
2863
2864 /* If there is a space or a new line both after and before the opener
2865 * (and if the code span is not made of spaces only), consume one initial
2866 * and one trailing space as part of the marks. */
2867 if(!has_only_space &&
2868 (has_space_after_opener || has_eol_after_opener) &&
2869 (has_space_before_closer || has_eol_before_closer))
2870 {
2871 if(has_space_after_opener)
2872 opener_end++;
2873 else
2874 opener_end = lines[1].beg;
2875
2876 if(has_space_before_closer)
2877 closer_beg--;
2878 else {
2879 /* Go back to the end of prev line */
2880 closer_beg = lines[line_index-1].end;
2881 /* But restore any trailing whitespace */
2882 while(closer_beg < ctx->size && ISBLANK(closer_beg))
2883 closer_beg++;
2884 }
2885 }
2886
2887 opener->ch = _T('`');
2888 opener->beg = opener_beg;
2889 opener->end = opener_end;
2890 opener->flags = MD_MARK_POTENTIAL_OPENER;
2891 closer->ch = _T('`');
2892 closer->beg = closer_beg;
2893 closer->end = closer_end;
2894 closer->flags = MD_MARK_POTENTIAL_CLOSER;
2895 return TRUE;
2896}
2897
2898static int
2899md_is_autolink_uri(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end)
2900{
2901 OFF off = beg+1;
2902
2903 MD_ASSERT(CH(beg) == _T('<'));
2904
2905 /* Check for scheme. */
2906 if(off >= max_end || !ISASCII(off))
2907 return FALSE;
2908 off++;
2909 while(1) {
2910 if(off >= max_end)
2911 return FALSE;
2912 if(off - beg > 32)
2913 return FALSE;
2914 if(CH(off) == _T(':') && off - beg >= 3)
2915 break;
2916 if(!ISALNUM(off) && CH(off) != _T('+') && CH(off) != _T('-') && CH(off) != _T('.'))
2917 return FALSE;
2918 off++;
2919 }
2920
2921 /* Check the path after the scheme. */
2922 while(off < max_end && CH(off) != _T('>')) {
2923 if(ISWHITESPACE(off) || ISCNTRL(off) || CH(off) == _T('<'))
2924 return FALSE;
2925 off++;
2926 }
2927
2928 if(off >= max_end)
2929 return FALSE;
2930
2931 MD_ASSERT(CH(off) == _T('>'));
2932 *p_end = off+1;
2933 return TRUE;
2934}
2935
2936static int
2937md_is_autolink_email(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end)
2938{
2939 OFF off = beg + 1;
2940 int label_len;
2941
2942 MD_ASSERT(CH(beg) == _T('<'));
2943
2944 /* The code should correspond to this regexp:
2945 /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+
2946 @[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?
2947 (?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
2948 */
2949
2950 /* Username (before '@'). */
2951 while(off < max_end && (ISALNUM(off) || ISANYOF(off, _T(".!#$%&'*+/=?^_`{|}~-"))))
2952 off++;
2953 if(off <= beg+1)
2954 return FALSE;
2955
2956 /* '@' */
2957 if(off >= max_end || CH(off) != _T('@'))
2958 return FALSE;
2959 off++;
2960
2961 /* Labels delimited with '.'; each label is sequence of 1 - 63 alnum
2962 * characters or '-', but '-' is not allowed as first or last char. */
2963 label_len = 0;
2964 while(off < max_end) {
2965 if(ISALNUM(off))
2966 label_len++;
2967 else if(CH(off) == _T('-') && label_len > 0)
2968 label_len++;
2969 else if(CH(off) == _T('.') && label_len > 0 && CH(off-1) != _T('-'))
2970 label_len = 0;
2971 else
2972 break;
2973
2974 if(label_len > 63)
2975 return FALSE;
2976
2977 off++;
2978 }
2979
2980 if(label_len <= 0 || off >= max_end || CH(off) != _T('>') || CH(off-1) == _T('-'))
2981 return FALSE;
2982
2983 *p_end = off+1;
2984 return TRUE;
2985}
2986
2987static int
2988md_is_autolink(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end, int* p_missing_mailto)
2989{
2990 if(md_is_autolink_uri(ctx, beg, max_end, p_end)) {
2991 *p_missing_mailto = FALSE;
2992 return TRUE;
2993 }
2994
2995 if(md_is_autolink_email(ctx, beg, max_end, p_end)) {
2996 *p_missing_mailto = TRUE;
2997 return TRUE;
2998 }
2999
3000 return FALSE;
3001}
3002
3003static int
3004md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, int table_mode)
3005{
3006 MD_SIZE line_index;
3007 int ret = 0;
3008 MD_MARK* mark;
3009 OFF codespan_last_potential_closers[CODESPAN_MARK_MAXLEN] = { 0 };
3010 int codespan_scanned_till_paragraph_end = FALSE;
3011
3012 for(line_index = 0; line_index < n_lines; line_index++) {
3013 const MD_LINE* line = &lines[line_index];
3014 OFF off = line->beg;
3015
3016 while(TRUE) {
3017 CHAR ch;
3018
3019#ifdef MD4C_USE_UTF16
3020 /* For UTF-16, mark_char_map[] covers only ASCII. */
3021 #define IS_MARK_CHAR(off) ((CH(off) < SIZEOF_ARRAY(ctx->mark_char_map)) && \
3022 (ctx->mark_char_map[(unsigned char) CH(off)]))
3023#else
3024 /* For 8-bit encodings, mark_char_map[] covers all 256 elements. */
3025 #define IS_MARK_CHAR(off) (ctx->mark_char_map[(unsigned char) CH(off)])
3026#endif
3027
3028 /* Optimization: Use some loop unrolling. */
3029 while(off + 3 < line->end && !IS_MARK_CHAR(off+0) && !IS_MARK_CHAR(off+1)
3030 && !IS_MARK_CHAR(off+2) && !IS_MARK_CHAR(off+3))
3031 off += 4;
3032 while(off < line->end && !IS_MARK_CHAR(off+0))
3033 off++;
3034
3035 if(off >= line->end)
3036 break;
3037
3038 ch = CH(off);
3039
3040 /* A backslash escape.
3041 * It can go beyond line->end as it may involve escaped new
3042 * line to form a hard break. */
3043 if(ch == _T('\\') && off+1 < ctx->size && (ISPUNCT(off+1) || ISNEWLINE(off+1))) {
3044 /* Hard-break cannot be on the last line of the block. */
3045 if(!ISNEWLINE(off+1) || line_index+1 < n_lines)
3046 ADD_MARK(ch, off, off+2, MD_MARK_RESOLVED);
3047 off += 2;
3048 continue;
3049 }
3050
3051 /* A potential (string) emphasis start/end. */
3052 if(ch == _T('*') || ch == _T('_')) {
3053 OFF tmp = off+1;
3054 int left_level; /* What precedes: 0 = whitespace; 1 = punctuation; 2 = other char. */
3055 int right_level; /* What follows: 0 = whitespace; 1 = punctuation; 2 = other char. */
3056
3057 while(tmp < line->end && CH(tmp) == ch)
3058 tmp++;
3059
3060 if(off == line->beg || ISUNICODEWHITESPACEBEFORE(off))
3061 left_level = 0;
3062 else if(ISUNICODEPUNCTBEFORE(off))
3063 left_level = 1;
3064 else
3065 left_level = 2;
3066
3067 if(tmp == line->end || ISUNICODEWHITESPACE(tmp))
3068 right_level = 0;
3069 else if(ISUNICODEPUNCT(tmp))
3070 right_level = 1;
3071 else
3072 right_level = 2;
3073
3074 /* Intra-word underscore doesn't have special meaning. */
3075 if(ch == _T('_') && left_level == 2 && right_level == 2) {
3076 left_level = 0;
3077 right_level = 0;
3078 }
3079
3080 if(left_level != 0 || right_level != 0) {
3081 unsigned flags = 0;
3082
3083 if(left_level > 0 && left_level >= right_level)
3084 flags |= MD_MARK_POTENTIAL_CLOSER;
3085 if(right_level > 0 && right_level >= left_level)
3086 flags |= MD_MARK_POTENTIAL_OPENER;
3087 if(flags == (MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER))
3088 flags |= MD_MARK_EMPH_OC;
3089
3090 /* For "the rule of three" we need to remember the original
3091 * size of the mark (modulo three), before we potentially
3092 * split the mark when being later resolved partially by some
3093 * shorter closer. */
3094 switch((tmp - off) % 3) {
3095 case 0: flags |= MD_MARK_EMPH_MOD3_0; break;
3096 case 1: flags |= MD_MARK_EMPH_MOD3_1; break;
3097 case 2: flags |= MD_MARK_EMPH_MOD3_2; break;
3098 }
3099
3100 ADD_MARK(ch, off, tmp, flags);
3101
3102 /* During resolving, multiple asterisks may have to be
3103 * split into independent span start/ends. Consider e.g.
3104 * "**foo* bar*". Therefore we push also some empty dummy
3105 * marks to have enough space for that. */
3106 off++;
3107 while(off < tmp) {
3108 ADD_MARK('D', off, off, 0);
3109 off++;
3110 }
3111 continue;
3112 }
3113
3114 off = tmp;
3115 continue;
3116 }
3117
3118 /* A potential code span start/end. */
3119 if(ch == _T('`')) {
3120 MD_MARK opener;
3121 MD_MARK closer;
3122 int is_code_span;
3123
3124 is_code_span = md_is_code_span(ctx, line, n_lines - line_index, off,
3125 &opener, &closer, codespan_last_potential_closers,
3126 &codespan_scanned_till_paragraph_end);
3127 if(is_code_span) {
3128 ADD_MARK(opener.ch, opener.beg, opener.end, opener.flags);
3129 ADD_MARK(closer.ch, closer.beg, closer.end, closer.flags);
3130 md_resolve_range(ctx, ctx->n_marks-2, ctx->n_marks-1);
3131 off = closer.end;
3132
3133 /* Advance the current line accordingly. */
3134 if(off > line->end)
3135 line = md_lookup_line(off, lines, n_lines, &line_index);
3136 continue;
3137 }
3138
3139 off = opener.end;
3140 continue;
3141 }
3142
3143 /* A potential entity start. */
3144 if(ch == _T('&')) {
3145 ADD_MARK(ch, off, off+1, MD_MARK_POTENTIAL_OPENER);
3146 off++;
3147 continue;
3148 }
3149
3150 /* A potential entity end. */
3151 if(ch == _T(';')) {
3152 /* We surely cannot be entity unless the previous mark is '&'. */
3153 if(ctx->n_marks > 0 && ctx->marks[ctx->n_marks-1].ch == _T('&'))
3154 ADD_MARK(ch, off, off+1, MD_MARK_POTENTIAL_CLOSER);
3155
3156 off++;
3157 continue;
3158 }
3159
3160 /* A potential autolink or raw HTML start/end. */
3161 if(ch == _T('<')) {
3162 int is_autolink;
3163 OFF autolink_end;
3164 int missing_mailto;
3165
3166 if(!(ctx->parser.flags & MD_FLAG_NOHTMLSPANS)) {
3167 int is_html;
3168 OFF html_end;
3169
3170 /* Given the nature of the raw HTML, we have to recognize
3171 * it here. Doing so later in md_analyze_lt_gt() could
3172 * open can of worms of quadratic complexity. */
3173 is_html = md_is_html_any(ctx, line, n_lines - line_index, off,
3174 lines[n_lines-1].end, &html_end);
3175 if(is_html) {
3176 ADD_MARK(_T('<'), off, off, MD_MARK_OPENER | MD_MARK_RESOLVED);
3177 ADD_MARK(_T('>'), html_end, html_end, MD_MARK_CLOSER | MD_MARK_RESOLVED);
3178 ctx->marks[ctx->n_marks-2].next = ctx->n_marks-1;
3179 ctx->marks[ctx->n_marks-1].prev = ctx->n_marks-2;
3180 off = html_end;
3181
3182 /* Advance the current line accordingly. */
3183 if(off > line->end)
3184 line = md_lookup_line(off, lines, n_lines, &line_index);
3185 continue;
3186 }
3187 }
3188
3189 is_autolink = md_is_autolink(ctx, off, lines[n_lines-1].end,
3190 &autolink_end, &missing_mailto);
3191 if(is_autolink) {
3192 unsigned flags = MD_MARK_RESOLVED | MD_MARK_AUTOLINK;
3193 if(missing_mailto)
3194 flags |= MD_MARK_AUTOLINK_MISSING_MAILTO;
3195
3196 ADD_MARK(_T('<'), off, off+1, MD_MARK_OPENER | flags);
3197 ADD_MARK(_T('>'), autolink_end-1, autolink_end, MD_MARK_CLOSER | flags);
3198 ctx->marks[ctx->n_marks-2].next = ctx->n_marks-1;
3199 ctx->marks[ctx->n_marks-1].prev = ctx->n_marks-2;
3200 off = autolink_end;
3201 continue;
3202 }
3203
3204 off++;
3205 continue;
3206 }
3207
3208 /* A potential link or its part. */
3209 if(ch == _T('[') || (ch == _T('!') && off+1 < line->end && CH(off+1) == _T('['))) {
3210 OFF tmp = (ch == _T('[') ? off+1 : off+2);
3211 ADD_MARK(ch, off, tmp, MD_MARK_POTENTIAL_OPENER);
3212 off = tmp;
3213 /* Two dummies to make enough place for data we need if it is
3214 * a link. */
3215 ADD_MARK('D', off, off, 0);
3216 ADD_MARK('D', off, off, 0);
3217 continue;
3218 }
3219 if(ch == _T(']')) {
3220 ADD_MARK(ch, off, off+1, MD_MARK_POTENTIAL_CLOSER);
3221 off++;
3222 continue;
3223 }
3224
3225 /* A potential permissive e-mail autolink. */
3226 if(ch == _T('@')) {
3227 if(line->beg + 1 <= off && ISALNUM(off-1) &&
3228 off + 3 < line->end && ISALNUM(off+1))
3229 {
3230 ADD_MARK(ch, off, off+1, MD_MARK_POTENTIAL_OPENER);
3231 /* Push a dummy as a reserve for a closer. */
3232 ADD_MARK('D', line->beg, line->end, 0);
3233 }
3234
3235 off++;
3236 continue;
3237 }
3238
3239 /* A potential permissive URL autolink. */
3240 if(ch == _T(':')) {
3241 static struct {
3242 const CHAR* scheme;
3243 SZ scheme_size;
3244 const CHAR* suffix;
3245 SZ suffix_size;
3246 } scheme_map[] = {
3247 /* In the order from the most frequently used, arguably. */
3248 { _T("http"), 4, _T("//"), 2 },
3249 { _T("https"), 5, _T("//"), 2 },
3250 { _T("ftp"), 3, _T("//"), 2 }
3251 };
3252 int scheme_index;
3253
3254 for(scheme_index = 0; scheme_index < (int) SIZEOF_ARRAY(scheme_map); scheme_index++) {
3255 const CHAR* scheme = scheme_map[scheme_index].scheme;
3256 const SZ scheme_size = scheme_map[scheme_index].scheme_size;
3257 const CHAR* suffix = scheme_map[scheme_index].suffix;
3258 const SZ suffix_size = scheme_map[scheme_index].suffix_size;
3259
3260 if(line->beg + scheme_size <= off && md_ascii_eq(STR(off-scheme_size), scheme, scheme_size) &&
3261 off + 1 + suffix_size < line->end && md_ascii_eq(STR(off+1), suffix, suffix_size))
3262 {
3263 ADD_MARK(ch, off-scheme_size, off+1+suffix_size, MD_MARK_POTENTIAL_OPENER);
3264 /* Push a dummy as a reserve for a closer. */
3265 ADD_MARK('D', line->beg, line->end, 0);
3266 off += 1 + suffix_size;
3267 break;
3268 }
3269 }
3270
3271 off++;
3272 continue;
3273 }
3274
3275 /* A potential permissive WWW autolink. */
3276 if(ch == _T('.')) {
3277 if(line->beg + 3 <= off && md_ascii_eq(STR(off-3), _T("www"), 3) &&
3278 (off-3 == line->beg || ISUNICODEWHITESPACEBEFORE(off-3) || ISUNICODEPUNCTBEFORE(off-3)))
3279 {
3280 ADD_MARK(ch, off-3, off+1, MD_MARK_POTENTIAL_OPENER);
3281 /* Push a dummy as a reserve for a closer. */
3282 ADD_MARK('D', line->beg, line->end, 0);
3283 off++;
3284 continue;
3285 }
3286
3287 off++;
3288 continue;
3289 }
3290
3291 /* A potential table cell boundary or wiki link label delimiter. */
3292 if((table_mode || ctx->parser.flags & MD_FLAG_WIKILINKS) && ch == _T('|')) {
3293 ADD_MARK(ch, off, off+1, 0);
3294 off++;
3295 continue;
3296 }
3297
3298 /* A potential strikethrough/equation start/end. */
3299 if(ch == _T('$') || ch == _T('~')) {
3300 OFF tmp = off+1;
3301
3302 while(tmp < line->end && CH(tmp) == ch)
3303 tmp++;
3304
3305 if(tmp - off <= 2) {
3306 unsigned flags = MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER;
3307
3308 if(off > line->beg && !ISUNICODEWHITESPACEBEFORE(off) && !ISUNICODEPUNCTBEFORE(off))
3309 flags &= ~MD_MARK_POTENTIAL_OPENER;
3310 if(tmp < line->end && !ISUNICODEWHITESPACE(tmp) && !ISUNICODEPUNCT(tmp))
3311 flags &= ~MD_MARK_POTENTIAL_CLOSER;
3312 if(flags != 0)
3313 ADD_MARK(ch, off, tmp, flags);
3314 }
3315
3316 off = tmp;
3317 continue;
3318 }
3319
3320 /* Turn non-trivial whitespace into single space. */
3321 if(ISWHITESPACE_(ch)) {
3322 OFF tmp = off+1;
3323
3324 while(tmp < line->end && ISWHITESPACE(tmp))
3325 tmp++;
3326
3327 if(tmp - off > 1 || ch != _T(' '))
3328 ADD_MARK(ch, off, tmp, MD_MARK_RESOLVED);
3329
3330 off = tmp;
3331 continue;
3332 }
3333
3334 /* NULL character. */
3335 if(ch == _T('\0')) {
3336 ADD_MARK(ch, off, off+1, MD_MARK_RESOLVED);
3337 off++;
3338 continue;
3339 }
3340
3341 off++;
3342 }
3343 }
3344
3345 /* Add a dummy mark at the end of the mark vector to simplify
3346 * process_inlines(). */
3347 ADD_MARK(127, ctx->size, ctx->size, MD_MARK_RESOLVED);
3348
3349abort:
3350 return ret;
3351}
3352
3353static void
3354md_analyze_bracket(MD_CTX* ctx, int mark_index)
3355{
3356 /* We cannot really resolve links here as for that we would need
3357 * more context. E.g. a following pair of brackets (reference link),
3358 * or enclosing pair of brackets (if the inner is the link, the outer
3359 * one cannot be.)
3360 *
3361 * Therefore we here only construct a list of '[' ']' pairs ordered by
3362 * position of the closer. This allows us to analyze what is or is not
3363 * link in the right order, from inside to outside in case of nested
3364 * brackets.
3365 *
3366 * The resolving itself is deferred to md_resolve_links().
3367 */
3368
3369 MD_MARK* mark = &ctx->marks[mark_index];
3370
3371 if(mark->flags & MD_MARK_POTENTIAL_OPENER) {
3372 if(BRACKET_OPENERS.top >= 0)
3373 ctx->marks[BRACKET_OPENERS.top].flags |= MD_MARK_HASNESTEDBRACKETS;
3374
3375 md_mark_stack_push(ctx, &BRACKET_OPENERS, mark_index);
3376 return;
3377 }
3378
3379 if(BRACKET_OPENERS.top >= 0) {
3380 int opener_index = md_mark_stack_pop(ctx, &BRACKET_OPENERS);
3381 MD_MARK* opener = &ctx->marks[opener_index];
3382
3383 /* Interconnect the opener and closer. */
3384 opener->next = mark_index;
3385 mark->prev = opener_index;
3386
3387 /* Add the pair into a list of potential links for md_resolve_links().
3388 * Note we misuse opener->prev for this as opener->next points to its
3389 * closer. */
3390 if(ctx->unresolved_link_tail >= 0)
3391 ctx->marks[ctx->unresolved_link_tail].prev = opener_index;
3392 else
3393 ctx->unresolved_link_head = opener_index;
3394 ctx->unresolved_link_tail = opener_index;
3395 opener->prev = -1;
3396 }
3397}
3398
3399/* Forward declaration. */
3400static void md_analyze_link_contents(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
3401 int mark_beg, int mark_end);
3402
3403static int
3404md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines)
3405{
3406 int opener_index = ctx->unresolved_link_head;
3407 OFF last_link_beg = 0;
3408 OFF last_link_end = 0;
3409 OFF last_img_beg = 0;
3410 OFF last_img_end = 0;
3411
3412 while(opener_index >= 0) {
3413 MD_MARK* opener = &ctx->marks[opener_index];
3414 int closer_index = opener->next;
3415 MD_MARK* closer = &ctx->marks[closer_index];
3416 int next_index = opener->prev;
3417 MD_MARK* next_opener;
3418 MD_MARK* next_closer;
3419 MD_LINK_ATTR attr;
3420 int is_link = FALSE;
3421
3422 if(next_index >= 0) {
3423 next_opener = &ctx->marks[next_index];
3424 next_closer = &ctx->marks[next_opener->next];
3425 } else {
3426 next_opener = NULL;
3427 next_closer = NULL;
3428 }
3429
3430 /* If nested ("[ [ ] ]"), we need to make sure that:
3431 * - The outer does not end inside of (...) belonging to the inner.
3432 * - The outer cannot be link if the inner is link (i.e. not image).
3433 *
3434 * (Note we here analyze from inner to outer as the marks are ordered
3435 * by closer->beg.)
3436 */
3437 if((opener->beg < last_link_beg && closer->end < last_link_end) ||
3438 (opener->beg < last_img_beg && closer->end < last_img_end) ||
3439 (opener->beg < last_link_end && opener->ch == '['))
3440 {
3441 opener_index = next_index;
3442 continue;
3443 }
3444
3445 /* Recognize and resolve wiki links.
3446 * Wiki-links maybe '[[destination]]' or '[[destination|label]]'.
3447 */
3448 if ((ctx->parser.flags & MD_FLAG_WIKILINKS) &&
3449 (opener->end - opener->beg == 1) && /* not image */
3450 next_opener != NULL && /* double '[' opener */
3451 next_opener->ch == '[' &&
3452 (next_opener->beg == opener->beg - 1) &&
3453 (next_opener->end - next_opener->beg == 1) &&
3454 next_closer != NULL && /* double ']' closer */
3455 next_closer->ch == ']' &&
3456 (next_closer->beg == closer->beg + 1) &&
3457 (next_closer->end - next_closer->beg == 1))
3458 {
3459 MD_MARK* delim = NULL;
3460 int delim_index;
3461 OFF dest_beg, dest_end;
3462
3463 is_link = TRUE;
3464
3465 /* We don't allow destination to be longer than 100 characters.
3466 * Lets scan to see whether there is '|'. (If not then the whole
3467 * wiki-link has to be below the 100 characters.) */
3468 delim_index = opener_index + 1;
3469 while(delim_index < closer_index) {
3470 MD_MARK* m = &ctx->marks[delim_index];
3471 if(m->ch == '|') {
3472 delim = m;
3473 break;
3474 }
3475 if(m->ch != 'D') {
3476 if(m->beg - opener->end > 100)
3477 break;
3478 if(m->ch != 'D' && (m->flags & MD_MARK_OPENER))
3479 delim_index = m->next;
3480 }
3481 delim_index++;
3482 }
3483
3484 dest_beg = opener->end;
3485 dest_end = (delim != NULL) ? delim->beg : closer->beg;
3486 if(dest_end - dest_beg == 0 || dest_end - dest_beg > 100)
3487 is_link = FALSE;
3488
3489 /* There may not be any new line in the destination. */
3490 if(is_link) {
3491 OFF off;
3492 for(off = dest_beg; off < dest_end; off++) {
3493 if(ISNEWLINE(off)) {
3494 is_link = FALSE;
3495 break;
3496 }
3497 }
3498 }
3499
3500 if(is_link) {
3501 if(delim != NULL) {
3502 if(delim->end < closer->beg) {
3503 md_rollback(ctx, opener_index, delim_index, MD_ROLLBACK_ALL);
3504 md_rollback(ctx, delim_index, closer_index, MD_ROLLBACK_CROSSING);
3505 delim->flags |= MD_MARK_RESOLVED;
3506 opener->end = delim->beg;
3507 } else {
3508 /* The pipe is just before the closer: [[foo|]] */
3509 md_rollback(ctx, opener_index, closer_index, MD_ROLLBACK_ALL);
3510 closer->beg = delim->beg;
3511 delim = NULL;
3512 }
3513 }
3514
3515 opener->beg = next_opener->beg;
3516 opener->next = closer_index;
3517 opener->flags |= MD_MARK_OPENER | MD_MARK_RESOLVED;
3518
3519 closer->end = next_closer->end;
3520 closer->prev = opener_index;
3521 closer->flags |= MD_MARK_CLOSER | MD_MARK_RESOLVED;
3522
3523 last_link_beg = opener->beg;
3524 last_link_end = closer->end;
3525
3526 if(delim != NULL)
3527 md_analyze_link_contents(ctx, lines, n_lines, delim_index+1, closer_index);
3528
3529 opener_index = next_opener->prev;
3530 continue;
3531 }
3532 }
3533
3534 if(next_opener != NULL && next_opener->beg == closer->end) {
3535 if(next_closer->beg > closer->end + 1) {
3536 /* Might be full reference link. */
3537 if(!(next_opener->flags & MD_MARK_HASNESTEDBRACKETS))
3538 is_link = md_is_link_reference(ctx, lines, n_lines, next_opener->beg, next_closer->end, &attr);
3539 } else {
3540 /* Might be shortcut reference link. */
3541 if(!(opener->flags & MD_MARK_HASNESTEDBRACKETS))
3542 is_link = md_is_link_reference(ctx, lines, n_lines, opener->beg, closer->end, &attr);
3543 }
3544
3545 if(is_link < 0)
3546 return -1;
3547
3548 if(is_link) {
3549 /* Eat the 2nd "[...]". */
3550 closer->end = next_closer->end;
3551
3552 /* Do not analyze the label as a standalone link in the next
3553 * iteration. */
3554 next_index = ctx->marks[next_index].prev;
3555 }
3556 } else {
3557 if(closer->end < ctx->size && CH(closer->end) == _T('(')) {
3558 /* Might be inline link. */
3559 OFF inline_link_end = UINT_MAX;
3560
3561 is_link = md_is_inline_link_spec(ctx, lines, n_lines, closer->end, &inline_link_end, &attr);
3562 if(is_link < 0)
3563 return -1;
3564
3565 /* Check the closing ')' is not inside an already resolved range
3566 * (i.e. a range with a higher priority), e.g. a code span. */
3567 if(is_link) {
3568 int i = closer_index + 1;
3569
3570 while(i < ctx->n_marks) {
3571 MD_MARK* mark = &ctx->marks[i];
3572
3573 if(mark->beg >= inline_link_end)
3574 break;
3575 if((mark->flags & (MD_MARK_OPENER | MD_MARK_RESOLVED)) == (MD_MARK_OPENER | MD_MARK_RESOLVED)) {
3576 if(ctx->marks[mark->next].beg >= inline_link_end) {
3577 /* Cancel the link status. */
3578 if(attr.title_needs_free)
3579 free(attr.title);
3580 is_link = FALSE;
3581 break;
3582 }
3583
3584 i = mark->next + 1;
3585 } else {
3586 i++;
3587 }
3588 }
3589 }
3590
3591 if(is_link) {
3592 /* Eat the "(...)" */
3593 closer->end = inline_link_end;
3594 }
3595 }
3596
3597 if(!is_link) {
3598 /* Might be collapsed reference link. */
3599 if(!(opener->flags & MD_MARK_HASNESTEDBRACKETS))
3600 is_link = md_is_link_reference(ctx, lines, n_lines, opener->beg, closer->end, &attr);
3601 if(is_link < 0)
3602 return -1;
3603 }
3604 }
3605
3606 if(is_link) {
3607 /* Resolve the brackets as a link. */
3608 opener->flags |= MD_MARK_OPENER | MD_MARK_RESOLVED;
3609 closer->flags |= MD_MARK_CLOSER | MD_MARK_RESOLVED;
3610
3611 /* If it is a link, we store the destination and title in the two
3612 * dummy marks after the opener. */
3613 MD_ASSERT(ctx->marks[opener_index+1].ch == 'D');
3614 ctx->marks[opener_index+1].beg = attr.dest_beg;
3615 ctx->marks[opener_index+1].end = attr.dest_end;
3616
3617 MD_ASSERT(ctx->marks[opener_index+2].ch == 'D');
3618 md_mark_store_ptr(ctx, opener_index+2, attr.title);
3619 /* The title might or might not have been allocated for us. */
3620 if(attr.title_needs_free)
3621 md_mark_stack_push(ctx, &ctx->ptr_stack, opener_index+2);
3622 ctx->marks[opener_index+2].prev = attr.title_size;
3623
3624 if(opener->ch == '[') {
3625 last_link_beg = opener->beg;
3626 last_link_end = closer->end;
3627 } else {
3628 last_img_beg = opener->beg;
3629 last_img_end = closer->end;
3630 }
3631
3632 md_analyze_link_contents(ctx, lines, n_lines, opener_index+1, closer_index);
3633
3634 /* If the link text is formed by nothing but permissive autolink,
3635 * suppress the autolink.
3636 * See https://github.com/mity/md4c/issues/152 for more info. */
3637 if(ctx->parser.flags & MD_FLAG_PERMISSIVEAUTOLINKS) {
3638 MD_MARK* first_nested;
3639 MD_MARK* last_nested;
3640
3641 first_nested = opener + 1;
3642 while(first_nested->ch == _T('D') && first_nested < closer)
3643 first_nested++;
3644
3645 last_nested = closer - 1;
3646 while(first_nested->ch == _T('D') && last_nested > opener)
3647 last_nested--;
3648
3649 if((first_nested->flags & MD_MARK_RESOLVED) &&
3650 first_nested->beg == opener->end &&
3651 ISANYOF_(first_nested->ch, _T("@:.")) &&
3652 first_nested->next == (last_nested - ctx->marks) &&
3653 last_nested->end == closer->beg)
3654 {
3655 first_nested->ch = _T('D');
3656 first_nested->flags &= ~MD_MARK_RESOLVED;
3657 last_nested->ch = _T('D');
3658 last_nested->flags &= ~MD_MARK_RESOLVED;
3659 }
3660 }
3661 }
3662
3663 opener_index = next_index;
3664 }
3665
3666 return 0;
3667}
3668
3669/* Analyze whether the mark '&' starts a HTML entity.
3670 * If so, update its flags as well as flags of corresponding closer ';'. */
3671static void
3672md_analyze_entity(MD_CTX* ctx, int mark_index)
3673{
3674 MD_MARK* opener = &ctx->marks[mark_index];
3675 MD_MARK* closer;
3676 OFF off;
3677
3678 /* Cannot be entity if there is no closer as the next mark.
3679 * (Any other mark between would mean strange character which cannot be
3680 * part of the entity.
3681 *
3682 * So we can do all the work on '&' and do not call this later for the
3683 * closing mark ';'.
3684 */
3685 if(mark_index + 1 >= ctx->n_marks)
3686 return;
3687 closer = &ctx->marks[mark_index+1];
3688 if(closer->ch != ';')
3689 return;
3690
3691 if(md_is_entity(ctx, opener->beg, closer->end, &off)) {
3692 MD_ASSERT(off == closer->end);
3693
3694 md_resolve_range(ctx, mark_index, mark_index+1);
3695 opener->end = closer->end;
3696 }
3697}
3698
3699static void
3700md_analyze_table_cell_boundary(MD_CTX* ctx, int mark_index)
3701{
3702 MD_MARK* mark = &ctx->marks[mark_index];
3703 mark->flags |= MD_MARK_RESOLVED;
3704 mark->next = -1;
3705
3706 if(ctx->table_cell_boundaries_head < 0)
3707 ctx->table_cell_boundaries_head = mark_index;
3708 else
3709 ctx->marks[ctx->table_cell_boundaries_tail].next = mark_index;
3710 ctx->table_cell_boundaries_tail = mark_index;
3711 ctx->n_table_cell_boundaries++;
3712}
3713
3714/* Split a longer mark into two. The new mark takes the given count of
3715 * characters. May only be called if an adequate number of dummy 'D' marks
3716 * follows.
3717 */
3718static int
3719md_split_emph_mark(MD_CTX* ctx, int mark_index, SZ n)
3720{
3721 MD_MARK* mark = &ctx->marks[mark_index];
3722 int new_mark_index = mark_index + (mark->end - mark->beg - n);
3723 MD_MARK* dummy = &ctx->marks[new_mark_index];
3724
3725 MD_ASSERT(mark->end - mark->beg > n);
3726 MD_ASSERT(dummy->ch == 'D');
3727
3728 memcpy(dummy, mark, sizeof(MD_MARK));
3729 mark->end -= n;
3730 dummy->beg = mark->end;
3731
3732 return new_mark_index;
3733}
3734
3735static void
3736md_analyze_emph(MD_CTX* ctx, int mark_index)
3737{
3738 MD_MARK* mark = &ctx->marks[mark_index];
3739
3740 /* If we can be a closer, try to resolve with the preceding opener. */
3741 if(mark->flags & MD_MARK_POTENTIAL_CLOSER) {
3742 MD_MARK* opener = NULL;
3743 int opener_index = 0;
3744 MD_MARKSTACK* opener_stacks[6];
3745 int i, n_opener_stacks;
3746 unsigned flags = mark->flags;
3747
3748 n_opener_stacks = 0;
3749
3750 /* Apply the rule of 3 */
3751 opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_0 | MD_MARK_EMPH_OC);
3752 if((flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_2)
3753 opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_1 | MD_MARK_EMPH_OC);
3754 if((flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_1)
3755 opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_2 | MD_MARK_EMPH_OC);
3756 opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_0);
3757 if(!(flags & MD_MARK_EMPH_OC) || (flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_2)
3758 opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_1);
3759 if(!(flags & MD_MARK_EMPH_OC) || (flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_1)
3760 opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_2);
3761
3762 /* Opener is the most recent mark from the allowed stacks. */
3763 for(i = 0; i < n_opener_stacks; i++) {
3764 if(opener_stacks[i]->top >= 0) {
3765 int m_index = opener_stacks[i]->top;
3766 MD_MARK* m = &ctx->marks[m_index];
3767
3768 if(opener == NULL || m->end > opener->end) {
3769 opener_index = m_index;
3770 opener = m;
3771 }
3772 }
3773 }
3774
3775 /* Resolve, if we have found matching opener. */
3776 if(opener != NULL) {
3777 SZ opener_size = opener->end - opener->beg;
3778 SZ closer_size = mark->end - mark->beg;
3779 MD_MARKSTACK* stack = md_opener_stack(ctx, opener_index);
3780
3781 if(opener_size > closer_size) {
3782 opener_index = md_split_emph_mark(ctx, opener_index, closer_size);
3783 md_mark_stack_push(ctx, stack, opener_index);
3784 } else if(opener_size < closer_size) {
3785 md_split_emph_mark(ctx, mark_index, closer_size - opener_size);
3786 }
3787
3788 /* Above we were only peeking. */
3789 md_mark_stack_pop(ctx, stack);
3790
3791 md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_CROSSING);
3792 md_resolve_range(ctx, opener_index, mark_index);
3793 return;
3794 }
3795 }
3796
3797 /* If we could not resolve as closer, we may be yet be an opener. */
3798 if(mark->flags & MD_MARK_POTENTIAL_OPENER)
3799 md_mark_stack_push(ctx, md_emph_stack(ctx, mark->ch, mark->flags), mark_index);
3800}
3801
3802static void
3803md_analyze_tilde(MD_CTX* ctx, int mark_index)
3804{
3805 MD_MARK* mark = &ctx->marks[mark_index];
3806 MD_MARKSTACK* stack = md_opener_stack(ctx, mark_index);
3807
3808 /* We attempt to be Github Flavored Markdown compatible here. GFM accepts
3809 * only tildes sequences of length 1 and 2, and the length of the opener
3810 * and closer has to match. */
3811
3812 if((mark->flags & MD_MARK_POTENTIAL_CLOSER) && stack->top >= 0) {
3813 int opener_index = stack->top;
3814
3815 md_mark_stack_pop(ctx, stack);
3816 md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_CROSSING);
3817 md_resolve_range(ctx, opener_index, mark_index);
3818 return;
3819 }
3820
3821 if(mark->flags & MD_MARK_POTENTIAL_OPENER)
3822 md_mark_stack_push(ctx, stack, mark_index);
3823}
3824
3825static void
3826md_analyze_dollar(MD_CTX* ctx, int mark_index)
3827{
3828 MD_MARK* mark = &ctx->marks[mark_index];
3829
3830 if((mark->flags & MD_MARK_POTENTIAL_CLOSER) && DOLLAR_OPENERS.top >= 0) {
3831 /* If the potential closer has a non-matching number of $, discard */
3832 MD_MARK* opener = &ctx->marks[DOLLAR_OPENERS.top];
3833 int opener_index = DOLLAR_OPENERS.top;
3834 MD_MARK* closer = mark;
3835 int closer_index = mark_index;
3836
3837 if(opener->end - opener->beg == closer->end - closer->beg) {
3838 /* We are the matching closer */
3839 md_mark_stack_pop(ctx, &DOLLAR_OPENERS);
3840 md_rollback(ctx, opener_index, closer_index, MD_ROLLBACK_ALL);
3841 md_resolve_range(ctx, opener_index, closer_index);
3842
3843 /* Discard all pending openers: Latex math span do not allow
3844 * nesting. */
3845 DOLLAR_OPENERS.top = -1;
3846 return;
3847 }
3848 }
3849
3850 if(mark->flags & MD_MARK_POTENTIAL_OPENER)
3851 md_mark_stack_push(ctx, &DOLLAR_OPENERS, mark_index);
3852}
3853
3854static MD_MARK*
3855md_scan_left_for_resolved_mark(MD_CTX* ctx, MD_MARK* mark_from, OFF off, MD_MARK** p_cursor)
3856{
3857 MD_MARK* mark;
3858
3859 for(mark = mark_from; mark >= ctx->marks; mark--) {
3860 if(mark->ch == 'D' || mark->beg > off)
3861 continue;
3862 if(mark->beg <= off && off < mark->end && (mark->flags & MD_MARK_RESOLVED)) {
3863 if(p_cursor != NULL)
3864 *p_cursor = mark;
3865 return mark;
3866 }
3867 if(mark->end <= off)
3868 break;
3869 }
3870
3871 if(p_cursor != NULL)
3872 *p_cursor = mark;
3873 return NULL;
3874}
3875
3876static MD_MARK*
3877md_scan_right_for_resolved_mark(MD_CTX* ctx, MD_MARK* mark_from, OFF off, MD_MARK** p_cursor)
3878{
3879 MD_MARK* mark;
3880
3881 for(mark = mark_from; mark < ctx->marks + ctx->n_marks; mark++) {
3882 if(mark->ch == 'D' || mark->end <= off)
3883 continue;
3884 if(mark->beg <= off && off < mark->end && (mark->flags & MD_MARK_RESOLVED)) {
3885 if(p_cursor != NULL)
3886 *p_cursor = mark;
3887 return mark;
3888 }
3889 if(mark->beg > off)
3890 break;
3891 }
3892
3893 if(p_cursor != NULL)
3894 *p_cursor = mark;
3895 return NULL;
3896}
3897
3898static void
3899md_analyze_permissive_autolink(MD_CTX* ctx, int mark_index)
3900{
3901 static const struct {
3902 const MD_CHAR start_char;
3903 const MD_CHAR delim_char;
3904 const MD_CHAR* allowed_nonalnum_chars;
3905 int min_components;
3906 const MD_CHAR optional_end_char;
3907 } URL_MAP[] = {
3908 { _T('\0'), _T('.'), _T(".-_"), 2, _T('\0') }, /* host, mandatory */
3909 { _T('/'), _T('/'), _T("/.-_"), 0, _T('/') }, /* path */
3910 { _T('?'), _T('&'), _T("&.-+_=()"), 1, _T('\0') }, /* query */
3911 { _T('#'), _T('\0'), _T(".-+_") , 1, _T('\0') } /* fragment */
3912 };
3913
3914 MD_MARK* opener = &ctx->marks[mark_index];
3915 MD_MARK* closer = &ctx->marks[mark_index + 1]; /* The dummy. */
3916 OFF line_beg = closer->beg; /* md_collect_mark() set this for us */
3917 OFF line_end = closer->end; /* ditto */
3918 OFF beg = opener->beg;
3919 OFF end = opener->end;
3920 MD_MARK* left_cursor = opener;
3921 int left_boundary_ok = FALSE;
3922 MD_MARK* right_cursor = opener;
3923 int right_boundary_ok = FALSE;
3924 unsigned i;
3925
3926 MD_ASSERT(closer->ch == 'D');
3927
3928 if(opener->ch == '@') {
3929 MD_ASSERT(CH(opener->beg) == _T('@'));
3930
3931 /* Scan backwards for the user name (before '@'). */
3932 while(beg > line_beg) {
3933 if(ISALNUM(beg-1))
3934 beg--;
3935 else if(beg >= line_beg+2 && ISALNUM(beg-2) &&
3936 ISANYOF(beg-1, _T(".-_+")) &&
3937 md_scan_left_for_resolved_mark(ctx, left_cursor, beg-1, &left_cursor) == NULL &&
3938 ISALNUM(beg))
3939 beg--;
3940 else
3941 break;
3942 }
3943 if(beg == opener->beg) /* empty user name */
3944 return;
3945 }
3946
3947 /* Verify there's line boundary, whitespace, allowed punctuation or
3948 * resolved emphasis mark just before the suspected autolink. */
3949 if(beg == line_beg || ISUNICODEWHITESPACEBEFORE(beg) || ISANYOF(beg-1, _T("({["))) {
3950 left_boundary_ok = TRUE;
3951 } else if(ISANYOF(beg-1, _T("*_~"))) {
3952 MD_MARK* left_mark;
3953
3954 left_mark = md_scan_left_for_resolved_mark(ctx, left_cursor, beg-1, &left_cursor);
3955 if(left_mark != NULL && (left_mark->flags & MD_MARK_OPENER))
3956 left_boundary_ok = TRUE;
3957 }
3958 if(!left_boundary_ok)
3959 return;
3960
3961 for(i = 0; i < SIZEOF_ARRAY(URL_MAP); i++) {
3962 int n_components = 0;
3963 int n_open_brackets = 0;
3964
3965 if(URL_MAP[i].start_char != _T('\0')) {
3966 if(end >= line_end || CH(end) != URL_MAP[i].start_char)
3967 continue;
3968 if(URL_MAP[i].min_components > 0 && (end+1 >= line_end || !ISALNUM(end+1)))
3969 continue;
3970 end++;
3971 }
3972
3973 while(end < line_end) {
3974 if(ISALNUM(end)) {
3975 if(n_components == 0)
3976 n_components++;
3977 end++;
3978 } else if(end < line_end &&
3979 ISANYOF(end, URL_MAP[i].allowed_nonalnum_chars) &&
3980 md_scan_right_for_resolved_mark(ctx, right_cursor, end, &right_cursor) == NULL &&
3981 ((end > line_beg && (ISALNUM(end-1) || CH(end-1) == _T(')'))) || CH(end) == _T('(')) &&
3982 ((end+1 < line_end && (ISALNUM(end+1) || CH(end+1) == _T('('))) || CH(end) == _T(')')))
3983 {
3984 if(CH(end) == URL_MAP[i].delim_char)
3985 n_components++;
3986
3987 /* brackets have to be balanced. */
3988 if(CH(end) == _T('(')) {
3989 n_open_brackets++;
3990 } else if(CH(end) == _T(')')) {
3991 if(n_open_brackets <= 0)
3992 break;
3993 n_open_brackets--;
3994 }
3995
3996 end++;
3997 } else {
3998 break;
3999 }
4000 }
4001
4002 if(end < line_end && URL_MAP[i].optional_end_char != _T('\0') &&
4003 CH(end) == URL_MAP[i].optional_end_char)
4004 end++;
4005
4006 if(n_components < URL_MAP[i].min_components || n_open_brackets != 0)
4007 return;
4008
4009 if(opener->ch == '@') /* E-mail autolinks wants only the host. */
4010 break;
4011 }
4012
4013 /* Verify there's line boundary, whitespace, allowed punctuation or
4014 * resolved emphasis mark just after the suspected autolink. */
4015 if(end == line_end || ISUNICODEWHITESPACE(end) || ISANYOF(end, _T(")}].!?,;"))) {
4016 right_boundary_ok = TRUE;
4017 } else {
4018 MD_MARK* right_mark;
4019
4020 right_mark = md_scan_right_for_resolved_mark(ctx, right_cursor, end, &right_cursor);
4021 if(right_mark != NULL && (right_mark->flags & MD_MARK_CLOSER))
4022 right_boundary_ok = TRUE;
4023 }
4024 if(!right_boundary_ok)
4025 return;
4026
4027 /* Success, we are an autolink. */
4028 opener->beg = beg;
4029 opener->end = beg;
4030 closer->beg = end;
4031 closer->end = end;
4032 closer->ch = opener->ch;
4033 md_resolve_range(ctx, mark_index, mark_index + 1);
4034}
4035
4036#define MD_ANALYZE_NOSKIP_EMPH 0x01
4037
4038static inline void
4039md_analyze_marks(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
4040 int mark_beg, int mark_end, const CHAR* mark_chars, unsigned flags)
4041{
4042 int i = mark_beg;
4043 OFF last_end = lines[0].beg;
4044
4045 MD_UNUSED(lines);
4046 MD_UNUSED(n_lines);
4047
4048 while(i < mark_end) {
4049 MD_MARK* mark = &ctx->marks[i];
4050
4051 /* Skip resolved spans. */
4052 if(mark->flags & MD_MARK_RESOLVED) {
4053 if((mark->flags & MD_MARK_OPENER) &&
4054 !((flags & MD_ANALYZE_NOSKIP_EMPH) && ISANYOF_(mark->ch, "*_~")))
4055 {
4056 MD_ASSERT(i < mark->next);
4057 i = mark->next + 1;
4058 } else {
4059 i++;
4060 }
4061 continue;
4062 }
4063
4064 /* Skip marks we do not want to deal with. */
4065 if(!ISANYOF_(mark->ch, mark_chars)) {
4066 i++;
4067 continue;
4068 }
4069
4070 /* The resolving in previous step could have expanded a mark. */
4071 if(mark->beg < last_end) {
4072 i++;
4073 continue;
4074 }
4075
4076 /* Analyze the mark. */
4077 switch(mark->ch) {
4078 case '[': /* Pass through. */
4079 case '!': /* Pass through. */
4080 case ']': md_analyze_bracket(ctx, i); break;
4081 case '&': md_analyze_entity(ctx, i); break;
4082 case '|': md_analyze_table_cell_boundary(ctx, i); break;
4083 case '_': /* Pass through. */
4084 case '*': md_analyze_emph(ctx, i); break;
4085 case '~': md_analyze_tilde(ctx, i); break;
4086 case '$': md_analyze_dollar(ctx, i); break;
4087 case '.': /* Pass through. */
4088 case ':': /* Pass through. */
4089 case '@': md_analyze_permissive_autolink(ctx, i); break;
4090 }
4091
4092 if(mark->flags & MD_MARK_RESOLVED) {
4093 if(mark->flags & MD_MARK_OPENER)
4094 last_end = ctx->marks[mark->next].end;
4095 else
4096 last_end = mark->end;
4097 }
4098
4099 i++;
4100 }
4101}
4102
4103/* Analyze marks (build ctx->marks). */
4104static int
4105md_analyze_inlines(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, int table_mode)
4106{
4107 int ret;
4108
4109 /* Reset the previously collected stack of marks. */
4110 ctx->n_marks = 0;
4111
4112 /* Collect all marks. */
4113 MD_CHECK(md_collect_marks(ctx, lines, n_lines, table_mode));
4114
4115 /* (1) Links. */
4116 md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("[]!"), 0);
4117 MD_CHECK(md_resolve_links(ctx, lines, n_lines));
4118 BRACKET_OPENERS.top = -1;
4119 ctx->unresolved_link_head = -1;
4120 ctx->unresolved_link_tail = -1;
4121
4122 if(table_mode) {
4123 /* (2) Analyze table cell boundaries. */
4124 MD_ASSERT(n_lines == 1);
4125 ctx->n_table_cell_boundaries = 0;
4126 md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("|"), 0);
4127 return ret;
4128 }
4129
4130 /* (3) Emphasis and strong emphasis; permissive autolinks. */
4131 md_analyze_link_contents(ctx, lines, n_lines, 0, ctx->n_marks);
4132
4133abort:
4134 return ret;
4135}
4136
4137static void
4138md_analyze_link_contents(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
4139 int mark_beg, int mark_end)
4140{
4141 int i;
4142
4143 md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, _T("&"), 0);
4144 md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, _T("*_~$"), 0);
4145
4146 if((ctx->parser.flags & MD_FLAG_PERMISSIVEAUTOLINKS) != 0) {
4147 /* These have to be processed last, as they may be greedy and expand
4148 * from their original mark. Also their implementation must be careful
4149 * not to cross any (previously) resolved marks when doing so. */
4150 md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, _T("@:."), MD_ANALYZE_NOSKIP_EMPH);
4151 }
4152
4153 for(i = 0; i < (int) SIZEOF_ARRAY(ctx->opener_stacks); i++)
4154 ctx->opener_stacks[i].top = -1;
4155}
4156
4157static int
4158md_enter_leave_span_a(MD_CTX* ctx, int enter, MD_SPANTYPE type,
4159 const CHAR* dest, SZ dest_size, int is_autolink,
4160 const CHAR* title, SZ title_size)
4161{
4162 MD_ATTRIBUTE_BUILD href_build = { 0 };
4163 MD_ATTRIBUTE_BUILD title_build = { 0 };
4164 MD_SPAN_A_DETAIL det;
4165 int ret = 0;
4166
4167 /* Note we here rely on fact that MD_SPAN_A_DETAIL and
4168 * MD_SPAN_IMG_DETAIL are binary-compatible. */
4169 memset(&det, 0, sizeof(MD_SPAN_A_DETAIL));
4170 MD_CHECK(md_build_attribute(ctx, dest, dest_size,
4171 (is_autolink ? MD_BUILD_ATTR_NO_ESCAPES : 0),
4172 &det.href, &href_build));
4173 MD_CHECK(md_build_attribute(ctx, title, title_size, 0, &det.title, &title_build));
4174 det.is_autolink = is_autolink;
4175 if(enter)
4176 MD_ENTER_SPAN(type, &det);
4177 else
4178 MD_LEAVE_SPAN(type, &det);
4179
4180abort:
4181 md_free_attribute(ctx, &href_build);
4182 md_free_attribute(ctx, &title_build);
4183 return ret;
4184}
4185
4186static int
4187md_enter_leave_span_wikilink(MD_CTX* ctx, int enter, const CHAR* target, SZ target_size)
4188{
4189 MD_ATTRIBUTE_BUILD target_build = { 0 };
4190 MD_SPAN_WIKILINK_DETAIL det;
4191 int ret = 0;
4192
4193 memset(&det, 0, sizeof(MD_SPAN_WIKILINK_DETAIL));
4194 MD_CHECK(md_build_attribute(ctx, target, target_size, 0, &det.target, &target_build));
4195
4196 if (enter)
4197 MD_ENTER_SPAN(MD_SPAN_WIKILINK, &det);
4198 else
4199 MD_LEAVE_SPAN(MD_SPAN_WIKILINK, &det);
4200
4201abort:
4202 md_free_attribute(ctx, &target_build);
4203 return ret;
4204}
4205
4206
4207/* Render the output, accordingly to the analyzed ctx->marks. */
4208static int
4209md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines)
4210{
4211 MD_TEXTTYPE text_type;
4212 const MD_LINE* line = lines;
4213 MD_MARK* prev_mark = NULL;
4214 MD_MARK* mark;
4215 OFF off = lines[0].beg;
4216 OFF end = lines[n_lines-1].end;
4217 OFF tmp;
4218 int enforce_hardbreak = 0;
4219 int ret = 0;
4220
4221 /* Find first resolved mark. Note there is always at least one resolved
4222 * mark, the dummy last one after the end of the latest line we actually
4223 * never really reach. This saves us of a lot of special checks and cases
4224 * in this function. */
4225 mark = ctx->marks;
4226 while(!(mark->flags & MD_MARK_RESOLVED))
4227 mark++;
4228
4229 text_type = MD_TEXT_NORMAL;
4230
4231 while(1) {
4232 /* Process the text up to the next mark or end-of-line. */
4233 tmp = (line->end < mark->beg ? line->end : mark->beg);
4234 if(tmp > off) {
4235 MD_TEXT(text_type, STR(off), tmp - off);
4236 off = tmp;
4237 }
4238
4239 /* If reached the mark, process it and move to next one. */
4240 if(off >= mark->beg) {
4241 switch(mark->ch) {
4242 case '\\': /* Backslash escape. */
4243 if(ISNEWLINE(mark->beg+1))
4244 enforce_hardbreak = 1;
4245 else
4246 MD_TEXT(text_type, STR(mark->beg+1), 1);
4247 break;
4248
4249 case ' ': /* Non-trivial space. */
4250 MD_TEXT(text_type, _T(" "), 1);
4251 break;
4252
4253 case '`': /* Code span. */
4254 if(mark->flags & MD_MARK_OPENER) {
4255 MD_ENTER_SPAN(MD_SPAN_CODE, NULL);
4256 text_type = MD_TEXT_CODE;
4257 } else {
4258 MD_LEAVE_SPAN(MD_SPAN_CODE, NULL);
4259 text_type = MD_TEXT_NORMAL;
4260 }
4261 break;
4262
4263 case '_': /* Underline (or emphasis if we fall through). */
4264 if(ctx->parser.flags & MD_FLAG_UNDERLINE) {
4265 if(mark->flags & MD_MARK_OPENER) {
4266 while(off < mark->end) {
4267 MD_ENTER_SPAN(MD_SPAN_U, NULL);
4268 off++;
4269 }
4270 } else {
4271 while(off < mark->end) {
4272 MD_LEAVE_SPAN(MD_SPAN_U, NULL);
4273 off++;
4274 }
4275 }
4276 break;
4277 }
4278 MD_FALLTHROUGH();
4279
4280 case '*': /* Emphasis, strong emphasis. */
4281 if(mark->flags & MD_MARK_OPENER) {
4282 if((mark->end - off) % 2) {
4283 MD_ENTER_SPAN(MD_SPAN_EM, NULL);
4284 off++;
4285 }
4286 while(off + 1 < mark->end) {
4287 MD_ENTER_SPAN(MD_SPAN_STRONG, NULL);
4288 off += 2;
4289 }
4290 } else {
4291 while(off + 1 < mark->end) {
4292 MD_LEAVE_SPAN(MD_SPAN_STRONG, NULL);
4293 off += 2;
4294 }
4295 if((mark->end - off) % 2) {
4296 MD_LEAVE_SPAN(MD_SPAN_EM, NULL);
4297 off++;
4298 }
4299 }
4300 break;
4301
4302 case '~':
4303 if(mark->flags & MD_MARK_OPENER)
4304 MD_ENTER_SPAN(MD_SPAN_DEL, NULL);
4305 else
4306 MD_LEAVE_SPAN(MD_SPAN_DEL, NULL);
4307 break;
4308
4309 case '$':
4310 if(mark->flags & MD_MARK_OPENER) {
4311 MD_ENTER_SPAN((mark->end - off) % 2 ? MD_SPAN_LATEXMATH : MD_SPAN_LATEXMATH_DISPLAY, NULL);
4312 text_type = MD_TEXT_LATEXMATH;
4313 } else {
4314 MD_LEAVE_SPAN((mark->end - off) % 2 ? MD_SPAN_LATEXMATH : MD_SPAN_LATEXMATH_DISPLAY, NULL);
4315 text_type = MD_TEXT_NORMAL;
4316 }
4317 break;
4318
4319 case '[': /* Link, wiki link, image. */
4320 case '!':
4321 case ']':
4322 {
4323 const MD_MARK* opener = (mark->ch != ']' ? mark : &ctx->marks[mark->prev]);
4324 const MD_MARK* closer = &ctx->marks[opener->next];
4325 const MD_MARK* dest_mark;
4326 const MD_MARK* title_mark;
4327
4328 if ((opener->ch == '[' && closer->ch == ']') &&
4329 opener->end - opener->beg >= 2 &&
4330 closer->end - closer->beg >= 2)
4331 {
4332 int has_label = (opener->end - opener->beg > 2);
4333 SZ target_sz;
4334
4335 if(has_label)
4336 target_sz = opener->end - (opener->beg+2);
4337 else
4338 target_sz = closer->beg - opener->end;
4339
4340 MD_CHECK(md_enter_leave_span_wikilink(ctx, (mark->ch != ']'),
4341 has_label ? STR(opener->beg+2) : STR(opener->end),
4342 target_sz));
4343
4344 break;
4345 }
4346
4347 dest_mark = opener+1;
4348 MD_ASSERT(dest_mark->ch == 'D');
4349 title_mark = opener+2;
4350 MD_ASSERT(title_mark->ch == 'D');
4351
4352 MD_CHECK(md_enter_leave_span_a(ctx, (mark->ch != ']'),
4353 (opener->ch == '!' ? MD_SPAN_IMG : MD_SPAN_A),
4354 STR(dest_mark->beg), dest_mark->end - dest_mark->beg, FALSE,
4355 md_mark_get_ptr(ctx, (int)(title_mark - ctx->marks)),
4356 title_mark->prev));
4357
4358 /* link/image closer may span multiple lines. */
4359 if(mark->ch == ']') {
4360 while(mark->end > line->end)
4361 line++;
4362 }
4363
4364 break;
4365 }
4366
4367 case '<':
4368 case '>': /* Autolink or raw HTML. */
4369 if(!(mark->flags & MD_MARK_AUTOLINK)) {
4370 /* Raw HTML. */
4371 if(mark->flags & MD_MARK_OPENER)
4372 text_type = MD_TEXT_HTML;
4373 else
4374 text_type = MD_TEXT_NORMAL;
4375 break;
4376 }
4377 /* Pass through, if auto-link. */
4378 MD_FALLTHROUGH();
4379
4380 case '@': /* Permissive e-mail autolink. */
4381 case ':': /* Permissive URL autolink. */
4382 case '.': /* Permissive WWW autolink. */
4383 {
4384 MD_MARK* opener = ((mark->flags & MD_MARK_OPENER) ? mark : &ctx->marks[mark->prev]);
4385 MD_MARK* closer = &ctx->marks[opener->next];
4386 const CHAR* dest = STR(opener->end);
4387 SZ dest_size = closer->beg - opener->end;
4388
4389 /* For permissive auto-links we do not know closer mark
4390 * position at the time of md_collect_marks(), therefore
4391 * it can be out-of-order in ctx->marks[].
4392 *
4393 * With this flag, we make sure that we output the closer
4394 * only if we processed the opener. */
4395 if(mark->flags & MD_MARK_OPENER)
4396 closer->flags |= MD_MARK_VALIDPERMISSIVEAUTOLINK;
4397
4398 if(opener->ch == '@' || opener->ch == '.' ||
4399 (opener->ch == '<' && (opener->flags & MD_MARK_AUTOLINK_MISSING_MAILTO)))
4400 {
4401 dest_size += 7;
4402 MD_TEMP_BUFFER(dest_size * sizeof(CHAR));
4403 memcpy(ctx->buffer,
4404 (opener->ch == '.' ? _T("http://") : _T("mailto:")),
4405 7 * sizeof(CHAR));
4406 memcpy(ctx->buffer + 7, dest, (dest_size-7) * sizeof(CHAR));
4407 dest = ctx->buffer;
4408 }
4409
4410 if(closer->flags & MD_MARK_VALIDPERMISSIVEAUTOLINK)
4411 MD_CHECK(md_enter_leave_span_a(ctx, (mark->flags & MD_MARK_OPENER),
4412 MD_SPAN_A, dest, dest_size, TRUE, NULL, 0));
4413 break;
4414 }
4415
4416 case '&': /* Entity. */
4417 MD_TEXT(MD_TEXT_ENTITY, STR(mark->beg), mark->end - mark->beg);
4418 break;
4419
4420 case '\0':
4421 MD_TEXT(MD_TEXT_NULLCHAR, _T(""), 1);
4422 break;
4423
4424 case 127:
4425 goto abort;
4426 }
4427
4428 off = mark->end;
4429
4430 /* Move to next resolved mark. */
4431 prev_mark = mark;
4432 mark++;
4433 while(!(mark->flags & MD_MARK_RESOLVED) || mark->beg < off)
4434 mark++;
4435 }
4436
4437 /* If reached end of line, move to next one. */
4438 if(off >= line->end) {
4439 /* If it is the last line, we are done. */
4440 if(off >= end)
4441 break;
4442
4443 if(text_type == MD_TEXT_CODE || text_type == MD_TEXT_LATEXMATH) {
4444 MD_ASSERT(prev_mark != NULL);
4445 MD_ASSERT(ISANYOF2_(prev_mark->ch, '`', '$') && (prev_mark->flags & MD_MARK_OPENER));
4446 MD_ASSERT(ISANYOF2_(mark->ch, '`', '$') && (mark->flags & MD_MARK_CLOSER));
4447
4448 /* Inside a code span, trailing line whitespace has to be
4449 * outputted. */
4450 tmp = off;
4451 while(off < ctx->size && ISBLANK(off))
4452 off++;
4453 if(off > tmp)
4454 MD_TEXT(text_type, STR(tmp), off-tmp);
4455
4456 /* and new lines are transformed into single spaces. */
4457 if(off == line->end)
4458 MD_TEXT(text_type, _T(" "), 1);
4459 } else if(text_type == MD_TEXT_HTML) {
4460 /* Inside raw HTML, we output the new line verbatim, including
4461 * any trailing spaces. */
4462 tmp = off;
4463 while(tmp < end && ISBLANK(tmp))
4464 tmp++;
4465 if(tmp > off)
4466 MD_TEXT(MD_TEXT_HTML, STR(off), tmp - off);
4467 MD_TEXT(MD_TEXT_HTML, _T("\n"), 1);
4468 } else {
4469 /* Output soft or hard line break. */
4470 MD_TEXTTYPE break_type = MD_TEXT_SOFTBR;
4471
4472 if(text_type == MD_TEXT_NORMAL) {
4473 if(enforce_hardbreak || (ctx->parser.flags & MD_FLAG_HARD_SOFT_BREAKS)) {
4474 break_type = MD_TEXT_BR;
4475 } else {
4476 while(off < ctx->size && ISBLANK(off))
4477 off++;
4478 if(off >= line->end + 2 && CH(off-2) == _T(' ') && CH(off-1) == _T(' ') && ISNEWLINE(off))
4479 break_type = MD_TEXT_BR;
4480 }
4481 }
4482
4483 MD_TEXT(break_type, _T("\n"), 1);
4484 }
4485
4486 /* Move to the next line. */
4487 line++;
4488 off = line->beg;
4489
4490 enforce_hardbreak = 0;
4491 }
4492 }
4493
4494abort:
4495 return ret;
4496}
4497
4498
4499/***************************
4500 *** Processing Tables ***
4501 ***************************/
4502
4503static void
4504md_analyze_table_alignment(MD_CTX* ctx, OFF beg, OFF end, MD_ALIGN* align, int n_align)
4505{
4506 static const MD_ALIGN align_map[] = { MD_ALIGN_DEFAULT, MD_ALIGN_LEFT, MD_ALIGN_RIGHT, MD_ALIGN_CENTER };
4507 OFF off = beg;
4508
4509 while(n_align > 0) {
4510 int index = 0; /* index into align_map[] */
4511
4512 while(CH(off) != _T('-'))
4513 off++;
4514 if(off > beg && CH(off-1) == _T(':'))
4515 index |= 1;
4516 while(off < end && CH(off) == _T('-'))
4517 off++;
4518 if(off < end && CH(off) == _T(':'))
4519 index |= 2;
4520
4521 *align = align_map[index];
4522 align++;
4523 n_align--;
4524 }
4525
4526}
4527
4528/* Forward declaration. */
4529static int md_process_normal_block_contents(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines);
4530
4531static int
4532md_process_table_cell(MD_CTX* ctx, MD_BLOCKTYPE cell_type, MD_ALIGN align, OFF beg, OFF end)
4533{
4534 MD_LINE line;
4535 MD_BLOCK_TD_DETAIL det;
4536 int ret = 0;
4537
4538 while(beg < end && ISWHITESPACE(beg))
4539 beg++;
4540 while(end > beg && ISWHITESPACE(end-1))
4541 end--;
4542
4543 det.align = align;
4544 line.beg = beg;
4545 line.end = end;
4546
4547 MD_ENTER_BLOCK(cell_type, &det);
4548 MD_CHECK(md_process_normal_block_contents(ctx, &line, 1));
4549 MD_LEAVE_BLOCK(cell_type, &det);
4550
4551abort:
4552 return ret;
4553}
4554
4555static int
4556md_process_table_row(MD_CTX* ctx, MD_BLOCKTYPE cell_type, OFF beg, OFF end,
4557 const MD_ALIGN* align, int col_count)
4558{
4559 MD_LINE line;
4560 OFF* pipe_offs = NULL;
4561 int i, j, k, n;
4562 int ret = 0;
4563
4564 line.beg = beg;
4565 line.end = end;
4566
4567 /* Break the line into table cells by identifying pipe characters who
4568 * form the cell boundary. */
4569 MD_CHECK(md_analyze_inlines(ctx, &line, 1, TRUE));
4570
4571 /* We have to remember the cell boundaries in local buffer because
4572 * ctx->marks[] shall be reused during cell contents processing. */
4573 n = ctx->n_table_cell_boundaries + 2;
4574 pipe_offs = (OFF*) malloc(n * sizeof(OFF));
4575 if(pipe_offs == NULL) {
4576 MD_LOG("malloc() failed.");
4577 ret = -1;
4578 goto abort;
4579 }
4580 j = 0;
4581 pipe_offs[j++] = beg;
4582 for(i = ctx->table_cell_boundaries_head; i >= 0; i = ctx->marks[i].next) {
4583 MD_MARK* mark = &ctx->marks[i];
4584 pipe_offs[j++] = mark->end;
4585 }
4586 pipe_offs[j++] = end+1;
4587
4588 /* Process cells. */
4589 MD_ENTER_BLOCK(MD_BLOCK_TR, NULL);
4590 k = 0;
4591 for(i = 0; i < j-1 && k < col_count; i++) {
4592 if(pipe_offs[i] < pipe_offs[i+1]-1)
4593 MD_CHECK(md_process_table_cell(ctx, cell_type, align[k++], pipe_offs[i], pipe_offs[i+1]-1));
4594 }
4595 /* Make sure we call enough table cells even if the current table contains
4596 * too few of them. */
4597 while(k < col_count)
4598 MD_CHECK(md_process_table_cell(ctx, cell_type, align[k++], 0, 0));
4599 MD_LEAVE_BLOCK(MD_BLOCK_TR, NULL);
4600
4601abort:
4602 free(pipe_offs);
4603
4604 ctx->table_cell_boundaries_head = -1;
4605 ctx->table_cell_boundaries_tail = -1;
4606
4607 return ret;
4608}
4609
4610static int
4611md_process_table_block_contents(MD_CTX* ctx, int col_count, const MD_LINE* lines, MD_SIZE n_lines)
4612{
4613 MD_ALIGN* align;
4614 MD_SIZE line_index;
4615 int ret = 0;
4616
4617 /* At least two lines have to be present: The column headers and the line
4618 * with the underlines. */
4619 MD_ASSERT(n_lines >= 2);
4620
4621 align = malloc(col_count * sizeof(MD_ALIGN));
4622 if(align == NULL) {
4623 MD_LOG("malloc() failed.");
4624 ret = -1;
4625 goto abort;
4626 }
4627
4628 md_analyze_table_alignment(ctx, lines[1].beg, lines[1].end, align, col_count);
4629
4630 MD_ENTER_BLOCK(MD_BLOCK_THEAD, NULL);
4631 MD_CHECK(md_process_table_row(ctx, MD_BLOCK_TH,
4632 lines[0].beg, lines[0].end, align, col_count));
4633 MD_LEAVE_BLOCK(MD_BLOCK_THEAD, NULL);
4634
4635 if(n_lines > 2) {
4636 MD_ENTER_BLOCK(MD_BLOCK_TBODY, NULL);
4637 for(line_index = 2; line_index < n_lines; line_index++) {
4638 MD_CHECK(md_process_table_row(ctx, MD_BLOCK_TD,
4639 lines[line_index].beg, lines[line_index].end, align, col_count));
4640 }
4641 MD_LEAVE_BLOCK(MD_BLOCK_TBODY, NULL);
4642 }
4643
4644abort:
4645 free(align);
4646 return ret;
4647}
4648
4649
4650/**************************
4651 *** Processing Block ***
4652 **************************/
4653
4654#define MD_BLOCK_CONTAINER_OPENER 0x01
4655#define MD_BLOCK_CONTAINER_CLOSER 0x02
4656#define MD_BLOCK_CONTAINER (MD_BLOCK_CONTAINER_OPENER | MD_BLOCK_CONTAINER_CLOSER)
4657#define MD_BLOCK_LOOSE_LIST 0x04
4658#define MD_BLOCK_SETEXT_HEADER 0x08
4659
4660struct MD_BLOCK_tag {
4661 MD_BLOCKTYPE type : 8;
4662 unsigned flags : 8;
4663
4664 /* MD_BLOCK_H: Header level (1 - 6)
4665 * MD_BLOCK_CODE: Non-zero if fenced, zero if indented.
4666 * MD_BLOCK_LI: Task mark character (0 if not task list item, 'x', 'X' or ' ').
4667 * MD_BLOCK_TABLE: Column count (as determined by the table underline).
4668 */
4669 unsigned data : 16;
4670
4671 /* Leaf blocks: Count of lines (MD_LINE or MD_VERBATIMLINE) on the block.
4672 * MD_BLOCK_LI: Task mark offset in the input doc.
4673 * MD_BLOCK_OL: Start item number.
4674 */
4675 MD_SIZE n_lines;
4676};
4677
4678struct MD_CONTAINER_tag {
4679 CHAR ch;
4680 unsigned is_loose : 8;
4681 unsigned is_task : 8;
4682 unsigned start;
4683 unsigned mark_indent;
4684 unsigned contents_indent;
4685 OFF block_byte_off;
4686 OFF task_mark_off;
4687};
4688
4689
4690static int
4691md_process_normal_block_contents(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines)
4692{
4693 int i;
4694 int ret;
4695
4696 MD_CHECK(md_analyze_inlines(ctx, lines, n_lines, FALSE));
4697 MD_CHECK(md_process_inlines(ctx, lines, n_lines));
4698
4699abort:
4700 /* Free any temporary memory blocks stored within some dummy marks. */
4701 for(i = ctx->ptr_stack.top; i >= 0; i = ctx->marks[i].next)
4702 free(md_mark_get_ptr(ctx, i));
4703 ctx->ptr_stack.top = -1;
4704
4705 return ret;
4706}
4707
4708static int
4709md_process_verbatim_block_contents(MD_CTX* ctx, MD_TEXTTYPE text_type, const MD_VERBATIMLINE* lines, MD_SIZE n_lines)
4710{
4711 static const CHAR indent_chunk_str[] = _T(" ");
4712 static const SZ indent_chunk_size = SIZEOF_ARRAY(indent_chunk_str) - 1;
4713
4714 MD_SIZE line_index;
4715 int ret = 0;
4716
4717 for(line_index = 0; line_index < n_lines; line_index++) {
4718 const MD_VERBATIMLINE* line = &lines[line_index];
4719 int indent = line->indent;
4720
4721 MD_ASSERT(indent >= 0);
4722
4723 /* Output code indentation. */
4724 while(indent > (int) indent_chunk_size) {
4725 MD_TEXT(text_type, indent_chunk_str, indent_chunk_size);
4726 indent -= indent_chunk_size;
4727 }
4728 if(indent > 0)
4729 MD_TEXT(text_type, indent_chunk_str, indent);
4730
4731 /* Output the code line itself. */
4732 MD_TEXT_INSECURE(text_type, STR(line->beg), line->end - line->beg);
4733
4734 /* Enforce end-of-line. */
4735 MD_TEXT(text_type, _T("\n"), 1);
4736 }
4737
4738abort:
4739 return ret;
4740}
4741
4742static int
4743md_process_code_block_contents(MD_CTX* ctx, int is_fenced, const MD_VERBATIMLINE* lines, MD_SIZE n_lines)
4744{
4745 if(is_fenced) {
4746 /* Skip the first line in case of fenced code: It is the fence.
4747 * (Only the starting fence is present due to logic in md_analyze_line().) */
4748 lines++;
4749 n_lines--;
4750 } else {
4751 /* Ignore blank lines at start/end of indented code block. */
4752 while(n_lines > 0 && lines[0].beg == lines[0].end) {
4753 lines++;
4754 n_lines--;
4755 }
4756 while(n_lines > 0 && lines[n_lines-1].beg == lines[n_lines-1].end) {
4757 n_lines--;
4758 }
4759 }
4760
4761 if(n_lines == 0)
4762 return 0;
4763
4764 return md_process_verbatim_block_contents(ctx, MD_TEXT_CODE, lines, n_lines);
4765}
4766
4767static int
4768md_setup_fenced_code_detail(MD_CTX* ctx, const MD_BLOCK* block, MD_BLOCK_CODE_DETAIL* det,
4769 MD_ATTRIBUTE_BUILD* info_build, MD_ATTRIBUTE_BUILD* lang_build)
4770{
4771 const MD_VERBATIMLINE* fence_line = (const MD_VERBATIMLINE*)(block + 1);
4772 OFF beg = fence_line->beg;
4773 OFF end = fence_line->end;
4774 OFF lang_end;
4775 CHAR fence_ch = CH(fence_line->beg);
4776 int ret = 0;
4777
4778 /* Skip the fence itself. */
4779 while(beg < ctx->size && CH(beg) == fence_ch)
4780 beg++;
4781 /* Trim initial spaces. */
4782 while(beg < ctx->size && CH(beg) == _T(' '))
4783 beg++;
4784
4785 /* Trim trailing spaces. */
4786 while(end > beg && CH(end-1) == _T(' '))
4787 end--;
4788
4789 /* Build info string attribute. */
4790 MD_CHECK(md_build_attribute(ctx, STR(beg), end - beg, 0, &det->info, info_build));
4791
4792 /* Build info string attribute. */
4793 lang_end = beg;
4794 while(lang_end < end && !ISWHITESPACE(lang_end))
4795 lang_end++;
4796 MD_CHECK(md_build_attribute(ctx, STR(beg), lang_end - beg, 0, &det->lang, lang_build));
4797
4798 det->fence_char = fence_ch;
4799
4800abort:
4801 return ret;
4802}
4803
4804static int
4805md_process_leaf_block(MD_CTX* ctx, const MD_BLOCK* block)
4806{
4807 union {
4808 MD_BLOCK_H_DETAIL header;
4809 MD_BLOCK_CODE_DETAIL code;
4810 MD_BLOCK_TABLE_DETAIL table;
4811 } det;
4812 MD_ATTRIBUTE_BUILD info_build;
4813 MD_ATTRIBUTE_BUILD lang_build;
4814 int is_in_tight_list;
4815 int clean_fence_code_detail = FALSE;
4816 int ret = 0;
4817
4818 memset(&det, 0, sizeof(det));
4819
4820 if(ctx->n_containers == 0)
4821 is_in_tight_list = FALSE;
4822 else
4823 is_in_tight_list = !ctx->containers[ctx->n_containers-1].is_loose;
4824
4825 switch(block->type) {
4826 case MD_BLOCK_H:
4827 det.header.level = block->data;
4828 break;
4829
4830 case MD_BLOCK_CODE:
4831 /* For fenced code block, we may need to set the info string. */
4832 if(block->data != 0) {
4833 memset(&det.code, 0, sizeof(MD_BLOCK_CODE_DETAIL));
4834 clean_fence_code_detail = TRUE;
4835 MD_CHECK(md_setup_fenced_code_detail(ctx, block, &det.code, &info_build, &lang_build));
4836 }
4837 break;
4838
4839 case MD_BLOCK_TABLE:
4840 det.table.col_count = block->data;
4841 det.table.head_row_count = 1;
4842 det.table.body_row_count = block->n_lines - 2;
4843 break;
4844
4845 default:
4846 /* Noop. */
4847 break;
4848 }
4849
4850 if(!is_in_tight_list || block->type != MD_BLOCK_P)
4851 MD_ENTER_BLOCK(block->type, (void*) &det);
4852
4853 /* Process the block contents accordingly to is type. */
4854 switch(block->type) {
4855 case MD_BLOCK_HR:
4856 /* noop */
4857 break;
4858
4859 case MD_BLOCK_CODE:
4860 MD_CHECK(md_process_code_block_contents(ctx, (block->data != 0),
4861 (const MD_VERBATIMLINE*)(block + 1), block->n_lines));
4862 break;
4863
4864 case MD_BLOCK_HTML:
4865 MD_CHECK(md_process_verbatim_block_contents(ctx, MD_TEXT_HTML,
4866 (const MD_VERBATIMLINE*)(block + 1), block->n_lines));
4867 break;
4868
4869 case MD_BLOCK_TABLE:
4870 MD_CHECK(md_process_table_block_contents(ctx, block->data,
4871 (const MD_LINE*)(block + 1), block->n_lines));
4872 break;
4873
4874 default:
4875 MD_CHECK(md_process_normal_block_contents(ctx,
4876 (const MD_LINE*)(block + 1), block->n_lines));
4877 break;
4878 }
4879
4880 if(!is_in_tight_list || block->type != MD_BLOCK_P)
4881 MD_LEAVE_BLOCK(block->type, (void*) &det);
4882
4883abort:
4884 if(clean_fence_code_detail) {
4885 md_free_attribute(ctx, &info_build);
4886 md_free_attribute(ctx, &lang_build);
4887 }
4888 return ret;
4889}
4890
4891static int
4892md_process_all_blocks(MD_CTX* ctx)
4893{
4894 int byte_off = 0;
4895 int ret = 0;
4896
4897 /* ctx->containers now is not needed for detection of lists and list items
4898 * so we reuse it for tracking what lists are loose or tight. We rely
4899 * on the fact the vector is large enough to hold the deepest nesting
4900 * level of lists. */
4901 ctx->n_containers = 0;
4902
4903 while(byte_off < ctx->n_block_bytes) {
4904 MD_BLOCK* block = (MD_BLOCK*)((char*)ctx->block_bytes + byte_off);
4905 union {
4906 MD_BLOCK_UL_DETAIL ul;
4907 MD_BLOCK_OL_DETAIL ol;
4908 MD_BLOCK_LI_DETAIL li;
4909 } det;
4910
4911 switch(block->type) {
4912 case MD_BLOCK_UL:
4913 det.ul.is_tight = (block->flags & MD_BLOCK_LOOSE_LIST) ? FALSE : TRUE;
4914 det.ul.mark = (CHAR) block->data;
4915 break;
4916
4917 case MD_BLOCK_OL:
4918 det.ol.start = block->n_lines;
4919 det.ol.is_tight = (block->flags & MD_BLOCK_LOOSE_LIST) ? FALSE : TRUE;
4920 det.ol.mark_delimiter = (CHAR) block->data;
4921 break;
4922
4923 case MD_BLOCK_LI:
4924 det.li.is_task = (block->data != 0);
4925 det.li.task_mark = (CHAR) block->data;
4926 det.li.task_mark_offset = (OFF) block->n_lines;
4927 break;
4928
4929 default:
4930 /* noop */
4931 break;
4932 }
4933
4934 if(block->flags & MD_BLOCK_CONTAINER) {
4935 if(block->flags & MD_BLOCK_CONTAINER_CLOSER) {
4936 MD_LEAVE_BLOCK(block->type, &det);
4937
4938 if(block->type == MD_BLOCK_UL || block->type == MD_BLOCK_OL || block->type == MD_BLOCK_QUOTE)
4939 ctx->n_containers--;
4940 }
4941
4942 if(block->flags & MD_BLOCK_CONTAINER_OPENER) {
4943 MD_ENTER_BLOCK(block->type, &det);
4944
4945 if(block->type == MD_BLOCK_UL || block->type == MD_BLOCK_OL) {
4946 ctx->containers[ctx->n_containers].is_loose = (block->flags & MD_BLOCK_LOOSE_LIST);
4947 ctx->n_containers++;
4948 } else if(block->type == MD_BLOCK_QUOTE) {
4949 /* This causes that any text in a block quote, even if
4950 * nested inside a tight list item, is wrapped with
4951 * <p>...</p>. */
4952 ctx->containers[ctx->n_containers].is_loose = TRUE;
4953 ctx->n_containers++;
4954 }
4955 }
4956 } else {
4957 MD_CHECK(md_process_leaf_block(ctx, block));
4958
4959 if(block->type == MD_BLOCK_CODE || block->type == MD_BLOCK_HTML)
4960 byte_off += block->n_lines * sizeof(MD_VERBATIMLINE);
4961 else
4962 byte_off += block->n_lines * sizeof(MD_LINE);
4963 }
4964
4965 byte_off += sizeof(MD_BLOCK);
4966 }
4967
4968 ctx->n_block_bytes = 0;
4969
4970abort:
4971 return ret;
4972}
4973
4974
4975/************************************
4976 *** Grouping Lines into Blocks ***
4977 ************************************/
4978
4979static void*
4980md_push_block_bytes(MD_CTX* ctx, int n_bytes)
4981{
4982 void* ptr;
4983
4984 if(ctx->n_block_bytes + n_bytes > ctx->alloc_block_bytes) {
4985 void* new_block_bytes;
4986
4987 ctx->alloc_block_bytes = (ctx->alloc_block_bytes > 0
4988 ? ctx->alloc_block_bytes + ctx->alloc_block_bytes / 2
4989 : 512);
4990 new_block_bytes = realloc(ctx->block_bytes, ctx->alloc_block_bytes);
4991 if(new_block_bytes == NULL) {
4992 MD_LOG("realloc() failed.");
4993 return NULL;
4994 }
4995
4996 /* Fix the ->current_block after the reallocation. */
4997 if(ctx->current_block != NULL) {
4998 OFF off_current_block = (OFF) ((char*) ctx->current_block - (char*) ctx->block_bytes);
4999 ctx->current_block = (MD_BLOCK*) ((char*) new_block_bytes + off_current_block);
5000 }
5001
5002 ctx->block_bytes = new_block_bytes;
5003 }
5004
5005 ptr = (char*)ctx->block_bytes + ctx->n_block_bytes;
5006 ctx->n_block_bytes += n_bytes;
5007 return ptr;
5008}
5009
5010static int
5011md_start_new_block(MD_CTX* ctx, const MD_LINE_ANALYSIS* line)
5012{
5013 MD_BLOCK* block;
5014
5015 MD_ASSERT(ctx->current_block == NULL);
5016
5017 block = (MD_BLOCK*) md_push_block_bytes(ctx, sizeof(MD_BLOCK));
5018 if(block == NULL)
5019 return -1;
5020
5021 switch(line->type) {
5022 case MD_LINE_HR:
5023 block->type = MD_BLOCK_HR;
5024 break;
5025
5026 case MD_LINE_ATXHEADER:
5027 case MD_LINE_SETEXTHEADER:
5028 block->type = MD_BLOCK_H;
5029 break;
5030
5031 case MD_LINE_FENCEDCODE:
5032 case MD_LINE_INDENTEDCODE:
5033 block->type = MD_BLOCK_CODE;
5034 break;
5035
5036 case MD_LINE_TEXT:
5037 block->type = MD_BLOCK_P;
5038 break;
5039
5040 case MD_LINE_HTML:
5041 block->type = MD_BLOCK_HTML;
5042 break;
5043
5044 case MD_LINE_BLANK:
5045 case MD_LINE_SETEXTUNDERLINE:
5046 case MD_LINE_TABLEUNDERLINE:
5047 default:
5048 MD_UNREACHABLE();
5049 break;
5050 }
5051
5052 block->flags = 0;
5053 block->data = line->data;
5054 block->n_lines = 0;
5055
5056 ctx->current_block = block;
5057 return 0;
5058}
5059
5060/* Eat from start of current (textual) block any reference definitions and
5061 * remember them so we can resolve any links referring to them.
5062 *
5063 * (Reference definitions can only be at start of it as they cannot break
5064 * a paragraph.)
5065 */
5066static int
5067md_consume_link_reference_definitions(MD_CTX* ctx)
5068{
5069 MD_LINE* lines = (MD_LINE*) (ctx->current_block + 1);
5070 MD_SIZE n_lines = ctx->current_block->n_lines;
5071 MD_SIZE n = 0;
5072
5073 /* Compute how many lines at the start of the block form one or more
5074 * reference definitions. */
5075 while(n < n_lines) {
5076 int n_link_ref_lines;
5077
5078 n_link_ref_lines = md_is_link_reference_definition(ctx,
5079 lines + n, n_lines - n);
5080 /* Not a reference definition? */
5081 if(n_link_ref_lines == 0)
5082 break;
5083
5084 /* We fail if it is the ref. def. but it could not be stored due
5085 * a memory allocation error. */
5086 if(n_link_ref_lines < 0)
5087 return -1;
5088
5089 n += n_link_ref_lines;
5090 }
5091
5092 /* If there was at least one reference definition, we need to remove
5093 * its lines from the block, or perhaps even the whole block. */
5094 if(n > 0) {
5095 if(n == n_lines) {
5096 /* Remove complete block. */
5097 ctx->n_block_bytes -= n * sizeof(MD_LINE);
5098 ctx->n_block_bytes -= sizeof(MD_BLOCK);
5099 ctx->current_block = NULL;
5100 } else {
5101 /* Remove just some initial lines from the block. */
5102 memmove(lines, lines + n, (n_lines - n) * sizeof(MD_LINE));
5103 ctx->current_block->n_lines -= n;
5104 ctx->n_block_bytes -= n * sizeof(MD_LINE);
5105 }
5106 }
5107
5108 return 0;
5109}
5110
5111static int
5112md_end_current_block(MD_CTX* ctx)
5113{
5114 int ret = 0;
5115
5116 if(ctx->current_block == NULL)
5117 return ret;
5118
5119 /* Check whether there is a reference definition. (We do this here instead
5120 * of in md_analyze_line() because reference definition can take multiple
5121 * lines.) */
5122 if(ctx->current_block->type == MD_BLOCK_P ||
5123 (ctx->current_block->type == MD_BLOCK_H && (ctx->current_block->flags & MD_BLOCK_SETEXT_HEADER)))
5124 {
5125 MD_LINE* lines = (MD_LINE*) (ctx->current_block + 1);
5126 if(lines[0].beg < ctx->size && CH(lines[0].beg) == _T('[')) {
5127 MD_CHECK(md_consume_link_reference_definitions(ctx));
5128 if(ctx->current_block == NULL)
5129 return ret;
5130 }
5131 }
5132
5133 if(ctx->current_block->type == MD_BLOCK_H && (ctx->current_block->flags & MD_BLOCK_SETEXT_HEADER)) {
5134 MD_SIZE n_lines = ctx->current_block->n_lines;
5135
5136 if(n_lines > 1) {
5137 /* Get rid of the underline. */
5138 ctx->current_block->n_lines--;
5139 ctx->n_block_bytes -= sizeof(MD_LINE);
5140 } else {
5141 /* Only the underline has left after eating the ref. defs.
5142 * Keep the line as beginning of a new ordinary paragraph. */
5143 ctx->current_block->type = MD_BLOCK_P;
5144 return 0;
5145 }
5146 }
5147
5148 /* Mark we are not building any block anymore. */
5149 ctx->current_block = NULL;
5150
5151abort:
5152 return ret;
5153}
5154
5155static int
5156md_add_line_into_current_block(MD_CTX* ctx, const MD_LINE_ANALYSIS* analysis)
5157{
5158 MD_ASSERT(ctx->current_block != NULL);
5159
5160 if(ctx->current_block->type == MD_BLOCK_CODE || ctx->current_block->type == MD_BLOCK_HTML) {
5161 MD_VERBATIMLINE* line;
5162
5163 line = (MD_VERBATIMLINE*) md_push_block_bytes(ctx, sizeof(MD_VERBATIMLINE));
5164 if(line == NULL)
5165 return -1;
5166
5167 line->indent = analysis->indent;
5168 line->beg = analysis->beg;
5169 line->end = analysis->end;
5170 } else {
5171 MD_LINE* line;
5172
5173 line = (MD_LINE*) md_push_block_bytes(ctx, sizeof(MD_LINE));
5174 if(line == NULL)
5175 return -1;
5176
5177 line->beg = analysis->beg;
5178 line->end = analysis->end;
5179 }
5180 ctx->current_block->n_lines++;
5181
5182 return 0;
5183}
5184
5185static int
5186md_push_container_bytes(MD_CTX* ctx, MD_BLOCKTYPE type, unsigned start,
5187 unsigned data, unsigned flags)
5188{
5189 MD_BLOCK* block;
5190 int ret = 0;
5191
5192 MD_CHECK(md_end_current_block(ctx));
5193
5194 block = (MD_BLOCK*) md_push_block_bytes(ctx, sizeof(MD_BLOCK));
5195 if(block == NULL)
5196 return -1;
5197
5198 block->type = type;
5199 block->flags = flags;
5200 block->data = data;
5201 block->n_lines = start;
5202
5203abort:
5204 return ret;
5205}
5206
5207
5208
5209/***********************
5210 *** Line Analysis ***
5211 ***********************/
5212
5213static int
5214md_is_hr_line(MD_CTX* ctx, OFF beg, OFF* p_end, OFF* p_killer)
5215{
5216 OFF off = beg + 1;
5217 int n = 1;
5218
5219 while(off < ctx->size && (CH(off) == CH(beg) || CH(off) == _T(' ') || CH(off) == _T('\t'))) {
5220 if(CH(off) == CH(beg))
5221 n++;
5222 off++;
5223 }
5224
5225 if(n < 3) {
5226 *p_killer = off;
5227 return FALSE;
5228 }
5229
5230 /* Nothing else can be present on the line. */
5231 if(off < ctx->size && !ISNEWLINE(off)) {
5232 *p_killer = off;
5233 return FALSE;
5234 }
5235
5236 *p_end = off;
5237 return TRUE;
5238}
5239
5240static int
5241md_is_atxheader_line(MD_CTX* ctx, OFF beg, OFF* p_beg, OFF* p_end, unsigned* p_level)
5242{
5243 int n;
5244 OFF off = beg + 1;
5245
5246 while(off < ctx->size && CH(off) == _T('#') && off - beg < 7)
5247 off++;
5248 n = off - beg;
5249
5250 if(n > 6)
5251 return FALSE;
5252 *p_level = n;
5253
5254 if(!(ctx->parser.flags & MD_FLAG_PERMISSIVEATXHEADERS) && off < ctx->size &&
5255 !ISBLANK(off) && !ISNEWLINE(off))
5256 return FALSE;
5257
5258 while(off < ctx->size && ISBLANK(off))
5259 off++;
5260 *p_beg = off;
5261 *p_end = off;
5262 return TRUE;
5263}
5264
5265static int
5266md_is_setext_underline(MD_CTX* ctx, OFF beg, OFF* p_end, unsigned* p_level)
5267{
5268 OFF off = beg + 1;
5269
5270 while(off < ctx->size && CH(off) == CH(beg))
5271 off++;
5272
5273 /* Optionally, space(s) or tabs can follow. */
5274 while(off < ctx->size && ISBLANK(off))
5275 off++;
5276
5277 /* But nothing more is allowed on the line. */
5278 if(off < ctx->size && !ISNEWLINE(off))
5279 return FALSE;
5280
5281 *p_level = (CH(beg) == _T('=') ? 1 : 2);
5282 *p_end = off;
5283 return TRUE;
5284}
5285
5286static int
5287md_is_table_underline(MD_CTX* ctx, OFF beg, OFF* p_end, unsigned* p_col_count)
5288{
5289 OFF off = beg;
5290 int found_pipe = FALSE;
5291 unsigned col_count = 0;
5292
5293 if(off < ctx->size && CH(off) == _T('|')) {
5294 found_pipe = TRUE;
5295 off++;
5296 while(off < ctx->size && ISWHITESPACE(off))
5297 off++;
5298 }
5299
5300 while(1) {
5301 int delimited = FALSE;
5302
5303 /* Cell underline ("-----", ":----", "----:" or ":----:") */
5304 if(off < ctx->size && CH(off) == _T(':'))
5305 off++;
5306 if(off >= ctx->size || CH(off) != _T('-'))
5307 return FALSE;
5308 while(off < ctx->size && CH(off) == _T('-'))
5309 off++;
5310 if(off < ctx->size && CH(off) == _T(':'))
5311 off++;
5312
5313 col_count++;
5314 if(col_count > TABLE_MAXCOLCOUNT) {
5315 MD_LOG("Suppressing table (column_count >" STRINGIZE(TABLE_MAXCOLCOUNT) ")");
5316 return FALSE;
5317 }
5318
5319 /* Pipe delimiter (optional at the end of line). */
5320 while(off < ctx->size && ISWHITESPACE(off))
5321 off++;
5322 if(off < ctx->size && CH(off) == _T('|')) {
5323 delimited = TRUE;
5324 found_pipe = TRUE;
5325 off++;
5326 while(off < ctx->size && ISWHITESPACE(off))
5327 off++;
5328 }
5329
5330 /* Success, if we reach end of line. */
5331 if(off >= ctx->size || ISNEWLINE(off))
5332 break;
5333
5334 if(!delimited)
5335 return FALSE;
5336 }
5337
5338 if(!found_pipe)
5339 return FALSE;
5340
5341 *p_end = off;
5342 *p_col_count = col_count;
5343 return TRUE;
5344}
5345
5346static int
5347md_is_opening_code_fence(MD_CTX* ctx, OFF beg, OFF* p_end)
5348{
5349 OFF off = beg;
5350
5351 while(off < ctx->size && CH(off) == CH(beg))
5352 off++;
5353
5354 /* Fence must have at least three characters. */
5355 if(off - beg < 3)
5356 return FALSE;
5357
5358 ctx->code_fence_length = off - beg;
5359
5360 /* Optionally, space(s) can follow. */
5361 while(off < ctx->size && CH(off) == _T(' '))
5362 off++;
5363
5364 /* Optionally, an info string can follow. */
5365 while(off < ctx->size && !ISNEWLINE(off)) {
5366 /* Backtick-based fence must not contain '`' in the info string. */
5367 if(CH(beg) == _T('`') && CH(off) == _T('`'))
5368 return FALSE;
5369 off++;
5370 }
5371
5372 *p_end = off;
5373 return TRUE;
5374}
5375
5376static int
5377md_is_closing_code_fence(MD_CTX* ctx, CHAR ch, OFF beg, OFF* p_end)
5378{
5379 OFF off = beg;
5380 int ret = FALSE;
5381
5382 /* Closing fence must have at least the same length and use same char as
5383 * opening one. */
5384 while(off < ctx->size && CH(off) == ch)
5385 off++;
5386 if(off - beg < ctx->code_fence_length)
5387 goto out;
5388
5389 /* Optionally, space(s) can follow */
5390 while(off < ctx->size && CH(off) == _T(' '))
5391 off++;
5392
5393 /* But nothing more is allowed on the line. */
5394 if(off < ctx->size && !ISNEWLINE(off))
5395 goto out;
5396
5397 ret = TRUE;
5398
5399out:
5400 /* Note we set *p_end even on failure: If we are not closing fence, caller
5401 * would eat the line anyway without any parsing. */
5402 *p_end = off;
5403 return ret;
5404}
5405
5406
5407/* Helper data for md_is_html_block_start_condition() and
5408 * md_is_html_block_end_condition() */
5409typedef struct TAG_tag TAG;
5410struct TAG_tag {
5411 const CHAR* name;
5412 unsigned len : 8;
5413};
5414
5415#ifdef X
5416 #undef X
5417#endif
5418#define X(name) { _T(name), (sizeof(name)-1) / sizeof(CHAR) }
5419#define Xend { NULL, 0 }
5420
5421static const TAG t1[] = { X("pre"), X("script"), X("style"), X("textarea"), Xend };
5422
5423static const TAG a6[] = { X("address"), X("article"), X("aside"), Xend };
5424static const TAG b6[] = { X("base"), X("basefont"), X("blockquote"), X("body"), Xend };
5425static const TAG c6[] = { X("caption"), X("center"), X("col"), X("colgroup"), Xend };
5426static const TAG d6[] = { X("dd"), X("details"), X("dialog"), X("dir"),
5427 X("div"), X("dl"), X("dt"), Xend };
5428static const TAG f6[] = { X("fieldset"), X("figcaption"), X("figure"), X("footer"),
5429 X("form"), X("frame"), X("frameset"), Xend };
5430static const TAG h6[] = { X("h1"), X("h2"), X("h3"), X("h4"), X("h5"), X("h6"),
5431 X("head"), X("header"), X("hr"), X("html"), Xend };
5432static const TAG i6[] = { X("iframe"), Xend };
5433static const TAG l6[] = { X("legend"), X("li"), X("link"), Xend };
5434static const TAG m6[] = { X("main"), X("menu"), X("menuitem"), Xend };
5435static const TAG n6[] = { X("nav"), X("noframes"), Xend };
5436static const TAG o6[] = { X("ol"), X("optgroup"), X("option"), Xend };
5437static const TAG p6[] = { X("p"), X("param"), Xend };
5438static const TAG s6[] = { X("search"), X("section"), X("summary"), Xend };
5439static const TAG t6[] = { X("table"), X("tbody"), X("td"), X("tfoot"), X("th"),
5440 X("thead"), X("title"), X("tr"), X("track"), Xend };
5441static const TAG u6[] = { X("ul"), Xend };
5442static const TAG xx[] = { Xend };
5443
5444#undef X
5445#undef Xend
5446
5447/* Returns type of the raw HTML block, or FALSE if it is not HTML block.
5448 * (Refer to CommonMark specification for details about the types.)
5449 */
5450static int
5451md_is_html_block_start_condition(MD_CTX* ctx, OFF beg)
5452{
5453 /* Type 6 is started by a long list of allowed tags. We use two-level
5454 * tree to speed-up the search. */
5455 static const TAG* map6[26] = {
5456 a6, b6, c6, d6, xx, f6, xx, h6, i6, xx, xx, l6, m6,
5457 n6, o6, p6, xx, xx, s6, t6, u6, xx, xx, xx, xx, xx
5458 };
5459 OFF off = beg + 1;
5460 int i;
5461
5462 /* Check for type 1: <script, <pre, or <style */
5463 for(i = 0; t1[i].name != NULL; i++) {
5464 if(off + t1[i].len <= ctx->size) {
5465 if(md_ascii_case_eq(STR(off), t1[i].name, t1[i].len))
5466 return 1;
5467 }
5468 }
5469
5470 /* Check for type 2: <!-- */
5471 if(off + 3 < ctx->size && CH(off) == _T('!') && CH(off+1) == _T('-') && CH(off+2) == _T('-'))
5472 return 2;
5473
5474 /* Check for type 3: <? */
5475 if(off < ctx->size && CH(off) == _T('?'))
5476 return 3;
5477
5478 /* Check for type 4 or 5: <! */
5479 if(off < ctx->size && CH(off) == _T('!')) {
5480 /* Check for type 4: <! followed by uppercase letter. */
5481 if(off + 1 < ctx->size && ISASCII(off+1))
5482 return 4;
5483
5484 /* Check for type 5: <![CDATA[ */
5485 if(off + 8 < ctx->size) {
5486 if(md_ascii_eq(STR(off), _T("![CDATA["), 8))
5487 return 5;
5488 }
5489 }
5490
5491 /* Check for type 6: Many possible starting tags listed above. */
5492 if(off + 1 < ctx->size && (ISALPHA(off) || (CH(off) == _T('/') && ISALPHA(off+1)))) {
5493 int slot;
5494 const TAG* tags;
5495
5496 if(CH(off) == _T('/'))
5497 off++;
5498
5499 slot = (ISUPPER(off) ? CH(off) - 'A' : CH(off) - 'a');
5500 tags = map6[slot];
5501
5502 for(i = 0; tags[i].name != NULL; i++) {
5503 if(off + tags[i].len <= ctx->size) {
5504 if(md_ascii_case_eq(STR(off), tags[i].name, tags[i].len)) {
5505 OFF tmp = off + tags[i].len;
5506 if(tmp >= ctx->size)
5507 return 6;
5508 if(ISBLANK(tmp) || ISNEWLINE(tmp) || CH(tmp) == _T('>'))
5509 return 6;
5510 if(tmp+1 < ctx->size && CH(tmp) == _T('/') && CH(tmp+1) == _T('>'))
5511 return 6;
5512 break;
5513 }
5514 }
5515 }
5516 }
5517
5518 /* Check for type 7: any COMPLETE other opening or closing tag. */
5519 if(off + 1 < ctx->size) {
5520 OFF end;
5521
5522 if(md_is_html_tag(ctx, NULL, 0, beg, ctx->size, &end)) {
5523 /* Only optional whitespace and new line may follow. */
5524 while(end < ctx->size && ISWHITESPACE(end))
5525 end++;
5526 if(end >= ctx->size || ISNEWLINE(end))
5527 return 7;
5528 }
5529 }
5530
5531 return FALSE;
5532}
5533
5534/* Case sensitive check whether there is a substring 'what' between 'beg'
5535 * and end of line. */
5536static int
5537md_line_contains(MD_CTX* ctx, OFF beg, const CHAR* what, SZ what_len, OFF* p_end)
5538{
5539 OFF i;
5540 for(i = beg; i + what_len < ctx->size; i++) {
5541 if(ISNEWLINE(i))
5542 break;
5543 if(memcmp(STR(i), what, what_len * sizeof(CHAR)) == 0) {
5544 *p_end = i + what_len;
5545 return TRUE;
5546 }
5547 }
5548
5549 *p_end = i;
5550 return FALSE;
5551}
5552
5553/* Returns type of HTML block end condition or FALSE if not an end condition.
5554 *
5555 * Note it fills p_end even when it is not end condition as the caller
5556 * does not need to analyze contents of a raw HTML block.
5557 */
5558static int
5559md_is_html_block_end_condition(MD_CTX* ctx, OFF beg, OFF* p_end)
5560{
5561 switch(ctx->html_block_type) {
5562 case 1:
5563 {
5564 OFF off = beg;
5565 int i;
5566
5567 while(off+1 < ctx->size && !ISNEWLINE(off)) {
5568 if(CH(off) == _T('<') && CH(off+1) == _T('/')) {
5569 for(i = 0; t1[i].name != NULL; i++) {
5570 if(off + 2 + t1[i].len < ctx->size) {
5571 if(md_ascii_case_eq(STR(off+2), t1[i].name, t1[i].len) &&
5572 CH(off+2+t1[i].len) == _T('>'))
5573 {
5574 *p_end = off+2+t1[i].len+1;
5575 return TRUE;
5576 }
5577 }
5578 }
5579 }
5580 off++;
5581 }
5582 *p_end = off;
5583 return FALSE;
5584 }
5585
5586 case 2:
5587 return (md_line_contains(ctx, beg, _T("-->"), 3, p_end) ? 2 : FALSE);
5588
5589 case 3:
5590 return (md_line_contains(ctx, beg, _T("?>"), 2, p_end) ? 3 : FALSE);
5591
5592 case 4:
5593 return (md_line_contains(ctx, beg, _T(">"), 1, p_end) ? 4 : FALSE);
5594
5595 case 5:
5596 return (md_line_contains(ctx, beg, _T("]]>"), 3, p_end) ? 5 : FALSE);
5597
5598 case 6: /* Pass through */
5599 case 7:
5600 if(beg >= ctx->size || ISNEWLINE(beg)) {
5601 /* Blank line ends types 6 and 7. */
5602 *p_end = beg;
5603 return ctx->html_block_type;
5604 }
5605 return FALSE;
5606
5607 default:
5608 MD_UNREACHABLE();
5609 }
5610 return FALSE;
5611}
5612
5613
5614static int
5615md_is_container_compatible(const MD_CONTAINER* pivot, const MD_CONTAINER* container)
5616{
5617 /* Block quote has no "items" like lists. */
5618 if(container->ch == _T('>'))
5619 return FALSE;
5620
5621 if(container->ch != pivot->ch)
5622 return FALSE;
5623 if(container->mark_indent > pivot->contents_indent)
5624 return FALSE;
5625
5626 return TRUE;
5627}
5628
5629static int
5630md_push_container(MD_CTX* ctx, const MD_CONTAINER* container)
5631{
5632 if(ctx->n_containers >= ctx->alloc_containers) {
5633 MD_CONTAINER* new_containers;
5634
5635 ctx->alloc_containers = (ctx->alloc_containers > 0
5636 ? ctx->alloc_containers + ctx->alloc_containers / 2
5637 : 16);
5638 new_containers = realloc(ctx->containers, ctx->alloc_containers * sizeof(MD_CONTAINER));
5639 if(new_containers == NULL) {
5640 MD_LOG("realloc() failed.");
5641 return -1;
5642 }
5643
5644 ctx->containers = new_containers;
5645 }
5646
5647 memcpy(&ctx->containers[ctx->n_containers++], container, sizeof(MD_CONTAINER));
5648 return 0;
5649}
5650
5651static int
5652md_enter_child_containers(MD_CTX* ctx, int n_children)
5653{
5654 int i;
5655 int ret = 0;
5656
5657 for(i = ctx->n_containers - n_children; i < ctx->n_containers; i++) {
5658 MD_CONTAINER* c = &ctx->containers[i];
5659 int is_ordered_list = FALSE;
5660
5661 switch(c->ch) {
5662 case _T(')'):
5663 case _T('.'):
5664 is_ordered_list = TRUE;
5665 MD_FALLTHROUGH();
5666
5667 case _T('-'):
5668 case _T('+'):
5669 case _T('*'):
5670 /* Remember offset in ctx->block_bytes so we can revisit the
5671 * block if we detect it is a loose list. */
5672 md_end_current_block(ctx);
5673 c->block_byte_off = ctx->n_block_bytes;
5674
5675 MD_CHECK(md_push_container_bytes(ctx,
5676 (is_ordered_list ? MD_BLOCK_OL : MD_BLOCK_UL),
5677 c->start, c->ch, MD_BLOCK_CONTAINER_OPENER));
5678 MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
5679 c->task_mark_off,
5680 (c->is_task ? CH(c->task_mark_off) : 0),
5681 MD_BLOCK_CONTAINER_OPENER));
5682 break;
5683
5684 case _T('>'):
5685 MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_QUOTE, 0, 0, MD_BLOCK_CONTAINER_OPENER));
5686 break;
5687
5688 default:
5689 MD_UNREACHABLE();
5690 break;
5691 }
5692 }
5693
5694abort:
5695 return ret;
5696}
5697
5698static int
5699md_leave_child_containers(MD_CTX* ctx, int n_keep)
5700{
5701 int ret = 0;
5702
5703 while(ctx->n_containers > n_keep) {
5704 MD_CONTAINER* c = &ctx->containers[ctx->n_containers-1];
5705 int is_ordered_list = FALSE;
5706
5707 switch(c->ch) {
5708 case _T(')'):
5709 case _T('.'):
5710 is_ordered_list = TRUE;
5711 MD_FALLTHROUGH();
5712
5713 case _T('-'):
5714 case _T('+'):
5715 case _T('*'):
5716 MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
5717 c->task_mark_off, (c->is_task ? CH(c->task_mark_off) : 0),
5718 MD_BLOCK_CONTAINER_CLOSER));
5719 MD_CHECK(md_push_container_bytes(ctx,
5720 (is_ordered_list ? MD_BLOCK_OL : MD_BLOCK_UL), 0,
5721 c->ch, MD_BLOCK_CONTAINER_CLOSER));
5722 break;
5723
5724 case _T('>'):
5725 MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_QUOTE, 0,
5726 0, MD_BLOCK_CONTAINER_CLOSER));
5727 break;
5728
5729 default:
5730 MD_UNREACHABLE();
5731 break;
5732 }
5733
5734 ctx->n_containers--;
5735 }
5736
5737abort:
5738 return ret;
5739}
5740
5741static int
5742md_is_container_mark(MD_CTX* ctx, unsigned indent, OFF beg, OFF* p_end, MD_CONTAINER* p_container)
5743{
5744 OFF off = beg;
5745 OFF max_end;
5746
5747 if(off >= ctx->size || indent >= ctx->code_indent_offset)
5748 return FALSE;
5749
5750 /* Check for block quote mark. */
5751 if(CH(off) == _T('>')) {
5752 off++;
5753 p_container->ch = _T('>');
5754 p_container->is_loose = FALSE;
5755 p_container->is_task = FALSE;
5756 p_container->mark_indent = indent;
5757 p_container->contents_indent = indent + 1;
5758 *p_end = off;
5759 return TRUE;
5760 }
5761
5762 /* Check for list item bullet mark. */
5763 if(ISANYOF(off, _T("-+*")) && (off+1 >= ctx->size || ISBLANK(off+1) || ISNEWLINE(off+1))) {
5764 p_container->ch = CH(off);
5765 p_container->is_loose = FALSE;
5766 p_container->is_task = FALSE;
5767 p_container->mark_indent = indent;
5768 p_container->contents_indent = indent + 1;
5769 *p_end = off+1;
5770 return TRUE;
5771 }
5772
5773 /* Check for ordered list item marks. */
5774 max_end = off + 9;
5775 if(max_end > ctx->size)
5776 max_end = ctx->size;
5777 p_container->start = 0;
5778 while(off < max_end && ISDIGIT(off)) {
5779 p_container->start = p_container->start * 10 + CH(off) - _T('0');
5780 off++;
5781 }
5782 if(off > beg &&
5783 off < ctx->size &&
5784 (CH(off) == _T('.') || CH(off) == _T(')')) &&
5785 (off+1 >= ctx->size || ISBLANK(off+1) || ISNEWLINE(off+1)))
5786 {
5787 p_container->ch = CH(off);
5788 p_container->is_loose = FALSE;
5789 p_container->is_task = FALSE;
5790 p_container->mark_indent = indent;
5791 p_container->contents_indent = indent + off - beg + 1;
5792 *p_end = off+1;
5793 return TRUE;
5794 }
5795
5796 return FALSE;
5797}
5798
5799static unsigned
5800md_line_indentation(MD_CTX* ctx, unsigned total_indent, OFF beg, OFF* p_end)
5801{
5802 OFF off = beg;
5803 unsigned indent = total_indent;
5804
5805 while(off < ctx->size && ISBLANK(off)) {
5806 if(CH(off) == _T('\t'))
5807 indent = (indent + 4) & ~3;
5808 else
5809 indent++;
5810 off++;
5811 }
5812
5813 *p_end = off;
5814 return indent - total_indent;
5815}
5816
5817static const MD_LINE_ANALYSIS md_dummy_blank_line = { MD_LINE_BLANK, 0, 0, 0, 0, 0 };
5818
5819/* Analyze type of the line and find some its properties. This serves as a
5820 * main input for determining type and boundaries of a block. */
5821static int
5822md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end,
5823 const MD_LINE_ANALYSIS* pivot_line, MD_LINE_ANALYSIS* line)
5824{
5825 unsigned total_indent = 0;
5826 int n_parents = 0;
5827 int n_brothers = 0;
5828 int n_children = 0;
5829 MD_CONTAINER container = { 0 };
5830 int prev_line_has_list_loosening_effect = ctx->last_line_has_list_loosening_effect;
5831 OFF off = beg;
5832 OFF hr_killer = 0;
5833 int ret = 0;
5834
5835 line->indent = md_line_indentation(ctx, total_indent, off, &off);
5836 total_indent += line->indent;
5837 line->beg = off;
5838 line->enforce_new_block = FALSE;
5839
5840 /* Given the indentation and block quote marks '>', determine how many of
5841 * the current containers are our parents. */
5842 while(n_parents < ctx->n_containers) {
5843 MD_CONTAINER* c = &ctx->containers[n_parents];
5844
5845 if(c->ch == _T('>') && line->indent < ctx->code_indent_offset &&
5846 off < ctx->size && CH(off) == _T('>'))
5847 {
5848 /* Block quote mark. */
5849 off++;
5850 total_indent++;
5851 line->indent = md_line_indentation(ctx, total_indent, off, &off);
5852 total_indent += line->indent;
5853
5854 /* The optional 1st space after '>' is part of the block quote mark. */
5855 if(line->indent > 0)
5856 line->indent--;
5857
5858 line->beg = off;
5859
5860 } else if(c->ch != _T('>') && line->indent >= c->contents_indent) {
5861 /* List. */
5862 line->indent -= c->contents_indent;
5863 } else {
5864 break;
5865 }
5866
5867 n_parents++;
5868 }
5869
5870 if(off >= ctx->size || ISNEWLINE(off)) {
5871 /* Blank line does not need any real indentation to be nested inside
5872 * a list. */
5873 if(n_brothers + n_children == 0) {
5874 while(n_parents < ctx->n_containers && ctx->containers[n_parents].ch != _T('>'))
5875 n_parents++;
5876 }
5877 }
5878
5879 while(TRUE) {
5880 /* Check whether we are fenced code continuation. */
5881 if(pivot_line->type == MD_LINE_FENCEDCODE) {
5882 line->beg = off;
5883
5884 /* We are another MD_LINE_FENCEDCODE unless we are closing fence
5885 * which we transform into MD_LINE_BLANK. */
5886 if(line->indent < ctx->code_indent_offset) {
5887 if(md_is_closing_code_fence(ctx, CH(pivot_line->beg), off, &off)) {
5888 line->type = MD_LINE_BLANK;
5889 ctx->last_line_has_list_loosening_effect = FALSE;
5890 break;
5891 }
5892 }
5893
5894 /* Change indentation accordingly to the initial code fence. */
5895 if(n_parents == ctx->n_containers) {
5896 if(line->indent > pivot_line->indent)
5897 line->indent -= pivot_line->indent;
5898 else
5899 line->indent = 0;
5900
5901 line->type = MD_LINE_FENCEDCODE;
5902 break;
5903 }
5904 }
5905
5906 /* Check whether we are HTML block continuation. */
5907 if(pivot_line->type == MD_LINE_HTML && ctx->html_block_type > 0) {
5908 if(n_parents < ctx->n_containers) {
5909 /* HTML block is implicitly ended if the enclosing container
5910 * block ends. */
5911 ctx->html_block_type = 0;
5912 } else {
5913 int html_block_type;
5914
5915 html_block_type = md_is_html_block_end_condition(ctx, off, &off);
5916 if(html_block_type > 0) {
5917 MD_ASSERT(html_block_type == ctx->html_block_type);
5918
5919 /* Make sure this is the last line of the block. */
5920 ctx->html_block_type = 0;
5921
5922 /* Some end conditions serve as blank lines at the same time. */
5923 if(html_block_type == 6 || html_block_type == 7) {
5924 line->type = MD_LINE_BLANK;
5925 line->indent = 0;
5926 break;
5927 }
5928 }
5929
5930 line->type = MD_LINE_HTML;
5931 n_parents = ctx->n_containers;
5932 break;
5933 }
5934 }
5935
5936 /* Check for blank line. */
5937 if(off >= ctx->size || ISNEWLINE(off)) {
5938 if(pivot_line->type == MD_LINE_INDENTEDCODE && n_parents == ctx->n_containers) {
5939 line->type = MD_LINE_INDENTEDCODE;
5940 if(line->indent > ctx->code_indent_offset)
5941 line->indent -= ctx->code_indent_offset;
5942 else
5943 line->indent = 0;
5944 ctx->last_line_has_list_loosening_effect = FALSE;
5945 } else {
5946 line->type = MD_LINE_BLANK;
5947 ctx->last_line_has_list_loosening_effect = (n_parents > 0 &&
5948 n_brothers + n_children == 0 &&
5949 ctx->containers[n_parents-1].ch != _T('>'));
5950
5951 #if 1
5952 /* See https://github.com/mity/md4c/issues/6
5953 *
5954 * This ugly checking tests we are in (yet empty) list item but
5955 * not its very first line (i.e. not the line with the list
5956 * item mark).
5957 *
5958 * If we are such a blank line, then any following non-blank
5959 * line which would be part of the list item actually has to
5960 * end the list because according to the specification, "a list
5961 * item can begin with at most one blank line."
5962 */
5963 if(n_parents > 0 && ctx->containers[n_parents-1].ch != _T('>') &&
5964 n_brothers + n_children == 0 && ctx->current_block == NULL &&
5965 ctx->n_block_bytes > (int) sizeof(MD_BLOCK))
5966 {
5967 MD_BLOCK* top_block = (MD_BLOCK*) ((char*)ctx->block_bytes + ctx->n_block_bytes - sizeof(MD_BLOCK));
5968 if(top_block->type == MD_BLOCK_LI)
5969 ctx->last_list_item_starts_with_two_blank_lines = TRUE;
5970 }
5971 #endif
5972 }
5973 break;
5974 } else {
5975 #if 1
5976 /* This is the 2nd half of the hack. If the flag is set (i.e. there
5977 * was a 2nd blank line at the beginning of the list item) and if
5978 * we would otherwise still belong to the list item, we enforce
5979 * the end of the list. */
5980 if(ctx->last_list_item_starts_with_two_blank_lines) {
5981 if(n_parents > 0 && n_parents == ctx->n_containers &&
5982 ctx->containers[n_parents-1].ch != _T('>') &&
5983 n_brothers + n_children == 0 && ctx->current_block == NULL &&
5984 ctx->n_block_bytes > (int) sizeof(MD_BLOCK))
5985 {
5986 MD_BLOCK* top_block = (MD_BLOCK*) ((char*)ctx->block_bytes + ctx->n_block_bytes - sizeof(MD_BLOCK));
5987 if(top_block->type == MD_BLOCK_LI) {
5988 n_parents--;
5989
5990 line->indent = total_indent;
5991 if(n_parents > 0)
5992 line->indent -= MIN(line->indent, ctx->containers[n_parents-1].contents_indent);
5993 }
5994 }
5995
5996 ctx->last_list_item_starts_with_two_blank_lines = FALSE;
5997 }
5998 #endif
5999 ctx->last_line_has_list_loosening_effect = FALSE;
6000 }
6001
6002 /* Check whether we are Setext underline. */
6003 if(line->indent < ctx->code_indent_offset && pivot_line->type == MD_LINE_TEXT
6004 && off < ctx->size && ISANYOF2(off, _T('='), _T('-'))
6005 && (n_parents == ctx->n_containers))
6006 {
6007 unsigned level;
6008
6009 if(md_is_setext_underline(ctx, off, &off, &level)) {
6010 line->type = MD_LINE_SETEXTUNDERLINE;
6011 line->data = level;
6012 break;
6013 }
6014 }
6015
6016 /* Check for thematic break line. */
6017 if(line->indent < ctx->code_indent_offset
6018 && off < ctx->size && off >= hr_killer
6019 && ISANYOF(off, _T("-_*")))
6020 {
6021 if(md_is_hr_line(ctx, off, &off, &hr_killer)) {
6022 line->type = MD_LINE_HR;
6023 break;
6024 }
6025 }
6026
6027 /* Check for "brother" container. I.e. whether we are another list item
6028 * in already started list. */
6029 if(n_parents < ctx->n_containers && n_brothers + n_children == 0) {
6030 OFF tmp;
6031
6032 if(md_is_container_mark(ctx, line->indent, off, &tmp, &container) &&
6033 md_is_container_compatible(&ctx->containers[n_parents], &container))
6034 {
6035 pivot_line = &md_dummy_blank_line;
6036
6037 off = tmp;
6038
6039 total_indent += container.contents_indent - container.mark_indent;
6040 line->indent = md_line_indentation(ctx, total_indent, off, &off);
6041 total_indent += line->indent;
6042 line->beg = off;
6043
6044 /* Some of the following whitespace actually still belongs to the mark. */
6045 if(off >= ctx->size || ISNEWLINE(off)) {
6046 container.contents_indent++;
6047 } else if(line->indent <= ctx->code_indent_offset) {
6048 container.contents_indent += line->indent;
6049 line->indent = 0;
6050 } else {
6051 container.contents_indent += 1;
6052 line->indent--;
6053 }
6054
6055 ctx->containers[n_parents].mark_indent = container.mark_indent;
6056 ctx->containers[n_parents].contents_indent = container.contents_indent;
6057
6058 n_brothers++;
6059 continue;
6060 }
6061 }
6062
6063 /* Check for indented code.
6064 * Note indented code block cannot interrupt a paragraph. */
6065 if(line->indent >= ctx->code_indent_offset && (pivot_line->type != MD_LINE_TEXT)) {
6066 line->type = MD_LINE_INDENTEDCODE;
6067 line->indent -= ctx->code_indent_offset;
6068 line->data = 0;
6069 break;
6070 }
6071
6072 /* Check for start of a new container block. */
6073 if(line->indent < ctx->code_indent_offset &&
6074 md_is_container_mark(ctx, line->indent, off, &off, &container))
6075 {
6076 if(pivot_line->type == MD_LINE_TEXT && n_parents == ctx->n_containers &&
6077 (off >= ctx->size || ISNEWLINE(off)) && container.ch != _T('>'))
6078 {
6079 /* Noop. List mark followed by a blank line cannot interrupt a paragraph. */
6080 } else if(pivot_line->type == MD_LINE_TEXT && n_parents == ctx->n_containers &&
6081 ISANYOF2_(container.ch, _T('.'), _T(')')) && container.start != 1)
6082 {
6083 /* Noop. Ordered list cannot interrupt a paragraph unless the start index is 1. */
6084 } else {
6085 total_indent += container.contents_indent - container.mark_indent;
6086 line->indent = md_line_indentation(ctx, total_indent, off, &off);
6087 total_indent += line->indent;
6088
6089 line->beg = off;
6090 line->data = container.ch;
6091
6092 /* Some of the following whitespace actually still belongs to the mark. */
6093 if(off >= ctx->size || ISNEWLINE(off)) {
6094 container.contents_indent++;
6095 } else if(line->indent <= ctx->code_indent_offset) {
6096 container.contents_indent += line->indent;
6097 line->indent = 0;
6098 } else {
6099 container.contents_indent += 1;
6100 line->indent--;
6101 }
6102
6103 if(n_brothers + n_children == 0)
6104 pivot_line = &md_dummy_blank_line;
6105
6106 if(n_children == 0)
6107 MD_CHECK(md_leave_child_containers(ctx, n_parents + n_brothers));
6108
6109 n_children++;
6110 MD_CHECK(md_push_container(ctx, &container));
6111 continue;
6112 }
6113 }
6114
6115 /* Check whether we are table continuation. */
6116 if(pivot_line->type == MD_LINE_TABLE && n_parents == ctx->n_containers) {
6117 line->type = MD_LINE_TABLE;
6118 break;
6119 }
6120
6121 /* Check for ATX header. */
6122 if(line->indent < ctx->code_indent_offset &&
6123 off < ctx->size && CH(off) == _T('#'))
6124 {
6125 unsigned level;
6126
6127 if(md_is_atxheader_line(ctx, off, &line->beg, &off, &level)) {
6128 line->type = MD_LINE_ATXHEADER;
6129 line->data = level;
6130 break;
6131 }
6132 }
6133
6134 /* Check whether we are starting code fence. */
6135 if(line->indent < ctx->code_indent_offset &&
6136 off < ctx->size && ISANYOF2(off, _T('`'), _T('~')))
6137 {
6138 if(md_is_opening_code_fence(ctx, off, &off)) {
6139 line->type = MD_LINE_FENCEDCODE;
6140 line->data = 1;
6141 line->enforce_new_block = TRUE;
6142 break;
6143 }
6144 }
6145
6146 /* Check for start of raw HTML block. */
6147 if(off < ctx->size && CH(off) == _T('<')
6148 && !(ctx->parser.flags & MD_FLAG_NOHTMLBLOCKS))
6149 {
6150 ctx->html_block_type = md_is_html_block_start_condition(ctx, off);
6151
6152 /* HTML block type 7 cannot interrupt paragraph. */
6153 if(ctx->html_block_type == 7 && pivot_line->type == MD_LINE_TEXT)
6154 ctx->html_block_type = 0;
6155
6156 if(ctx->html_block_type > 0) {
6157 /* The line itself also may immediately close the block. */
6158 if(md_is_html_block_end_condition(ctx, off, &off) == ctx->html_block_type) {
6159 /* Make sure this is the last line of the block. */
6160 ctx->html_block_type = 0;
6161 }
6162
6163 line->enforce_new_block = TRUE;
6164 line->type = MD_LINE_HTML;
6165 break;
6166 }
6167 }
6168
6169 /* Check for table underline. */
6170 if((ctx->parser.flags & MD_FLAG_TABLES) && pivot_line->type == MD_LINE_TEXT
6171 && off < ctx->size && ISANYOF3(off, _T('|'), _T('-'), _T(':'))
6172 && n_parents == ctx->n_containers)
6173 {
6174 unsigned col_count;
6175
6176 if(ctx->current_block != NULL && ctx->current_block->n_lines == 1 &&
6177 md_is_table_underline(ctx, off, &off, &col_count))
6178 {
6179 line->data = col_count;
6180 line->type = MD_LINE_TABLEUNDERLINE;
6181 break;
6182 }
6183 }
6184
6185 /* By default, we are normal text line. */
6186 line->type = MD_LINE_TEXT;
6187 if(pivot_line->type == MD_LINE_TEXT && n_brothers + n_children == 0) {
6188 /* Lazy continuation. */
6189 n_parents = ctx->n_containers;
6190 }
6191
6192 /* Check for task mark. */
6193 if((ctx->parser.flags & MD_FLAG_TASKLISTS) && n_brothers + n_children > 0 &&
6194 ISANYOF_(ctx->containers[ctx->n_containers-1].ch, _T("-+*.)")))
6195 {
6196 OFF tmp = off;
6197
6198 while(tmp < ctx->size && tmp < off + 3 && ISBLANK(tmp))
6199 tmp++;
6200 if(tmp + 2 < ctx->size && CH(tmp) == _T('[') &&
6201 ISANYOF(tmp+1, _T("xX ")) && CH(tmp+2) == _T(']') &&
6202 (tmp + 3 == ctx->size || ISBLANK(tmp+3) || ISNEWLINE(tmp+3)))
6203 {
6204 MD_CONTAINER* task_container = (n_children > 0 ? &ctx->containers[ctx->n_containers-1] : &container);
6205 task_container->is_task = TRUE;
6206 task_container->task_mark_off = tmp + 1;
6207 off = tmp + 3;
6208 while(off < ctx->size && ISWHITESPACE(off))
6209 off++;
6210 line->beg = off;
6211 }
6212 }
6213
6214 break;
6215 }
6216
6217 /* Scan for end of the line.
6218 *
6219 * Note this is quite a bottleneck of the parsing as we here iterate almost
6220 * over compete document.
6221 */
6222#if defined __linux__ && !defined MD4C_USE_UTF16
6223 /* Recent glibc versions have superbly optimized strcspn(), even using
6224 * vectorization if available. */
6225 if(ctx->doc_ends_with_newline && off < ctx->size) {
6226 while(TRUE) {
6227 off += (OFF) strcspn(STR(off), "\r\n");
6228
6229 /* strcspn() can stop on zero terminator; but that can appear
6230 * anywhere in the Markfown input... */
6231 if(CH(off) == _T('\0'))
6232 off++;
6233 else
6234 break;
6235 }
6236 } else
6237#endif
6238 {
6239 /* Optimization: Use some loop unrolling. */
6240 while(off + 3 < ctx->size && !ISNEWLINE(off+0) && !ISNEWLINE(off+1)
6241 && !ISNEWLINE(off+2) && !ISNEWLINE(off+3))
6242 off += 4;
6243 while(off < ctx->size && !ISNEWLINE(off))
6244 off++;
6245 }
6246
6247 /* Set end of the line. */
6248 line->end = off;
6249
6250 /* But for ATX header, we should exclude the optional trailing mark. */
6251 if(line->type == MD_LINE_ATXHEADER) {
6252 OFF tmp = line->end;
6253 while(tmp > line->beg && ISBLANK(tmp-1))
6254 tmp--;
6255 while(tmp > line->beg && CH(tmp-1) == _T('#'))
6256 tmp--;
6257 if(tmp == line->beg || ISBLANK(tmp-1) || (ctx->parser.flags & MD_FLAG_PERMISSIVEATXHEADERS))
6258 line->end = tmp;
6259 }
6260
6261 /* Trim trailing spaces. */
6262 if(line->type != MD_LINE_INDENTEDCODE && line->type != MD_LINE_FENCEDCODE && line->type != MD_LINE_HTML) {
6263 while(line->end > line->beg && ISBLANK(line->end-1))
6264 line->end--;
6265 }
6266
6267 /* Eat also the new line. */
6268 if(off < ctx->size && CH(off) == _T('\r'))
6269 off++;
6270 if(off < ctx->size && CH(off) == _T('\n'))
6271 off++;
6272
6273 *p_end = off;
6274
6275 /* If we belong to a list after seeing a blank line, the list is loose. */
6276 if(prev_line_has_list_loosening_effect && line->type != MD_LINE_BLANK && n_parents + n_brothers > 0) {
6277 MD_CONTAINER* c = &ctx->containers[n_parents + n_brothers - 1];
6278 if(c->ch != _T('>')) {
6279 MD_BLOCK* block = (MD_BLOCK*) (((char*)ctx->block_bytes) + c->block_byte_off);
6280 block->flags |= MD_BLOCK_LOOSE_LIST;
6281 }
6282 }
6283
6284 /* Leave any containers we are not part of anymore. */
6285 if(n_children == 0 && n_parents + n_brothers < ctx->n_containers)
6286 MD_CHECK(md_leave_child_containers(ctx, n_parents + n_brothers));
6287
6288 /* Enter any container we found a mark for. */
6289 if(n_brothers > 0) {
6290 MD_ASSERT(n_brothers == 1);
6291 MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
6292 ctx->containers[n_parents].task_mark_off,
6293 (ctx->containers[n_parents].is_task ? CH(ctx->containers[n_parents].task_mark_off) : 0),
6294 MD_BLOCK_CONTAINER_CLOSER));
6295 MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
6296 container.task_mark_off,
6297 (container.is_task ? CH(container.task_mark_off) : 0),
6298 MD_BLOCK_CONTAINER_OPENER));
6299 ctx->containers[n_parents].is_task = container.is_task;
6300 ctx->containers[n_parents].task_mark_off = container.task_mark_off;
6301 }
6302
6303 if(n_children > 0)
6304 MD_CHECK(md_enter_child_containers(ctx, n_children));
6305
6306abort:
6307 return ret;
6308}
6309
6310static int
6311md_process_line(MD_CTX* ctx, const MD_LINE_ANALYSIS** p_pivot_line, MD_LINE_ANALYSIS* line)
6312{
6313 const MD_LINE_ANALYSIS* pivot_line = *p_pivot_line;
6314 int ret = 0;
6315
6316 /* Blank line ends current leaf block. */
6317 if(line->type == MD_LINE_BLANK) {
6318 MD_CHECK(md_end_current_block(ctx));
6319 *p_pivot_line = &md_dummy_blank_line;
6320 return 0;
6321 }
6322
6323 if(line->enforce_new_block)
6324 MD_CHECK(md_end_current_block(ctx));
6325
6326 /* Some line types form block on their own. */
6327 if(line->type == MD_LINE_HR || line->type == MD_LINE_ATXHEADER) {
6328 MD_CHECK(md_end_current_block(ctx));
6329
6330 /* Add our single-line block. */
6331 MD_CHECK(md_start_new_block(ctx, line));
6332 MD_CHECK(md_add_line_into_current_block(ctx, line));
6333 MD_CHECK(md_end_current_block(ctx));
6334 *p_pivot_line = &md_dummy_blank_line;
6335 return 0;
6336 }
6337
6338 /* MD_LINE_SETEXTUNDERLINE changes meaning of the current block and ends it. */
6339 if(line->type == MD_LINE_SETEXTUNDERLINE) {
6340 MD_ASSERT(ctx->current_block != NULL);
6341 ctx->current_block->type = MD_BLOCK_H;
6342 ctx->current_block->data = line->data;
6343 ctx->current_block->flags |= MD_BLOCK_SETEXT_HEADER;
6344 MD_CHECK(md_add_line_into_current_block(ctx, line));
6345 MD_CHECK(md_end_current_block(ctx));
6346 if(ctx->current_block == NULL) {
6347 *p_pivot_line = &md_dummy_blank_line;
6348 } else {
6349 /* This happens if we have consumed all the body as link ref. defs.
6350 * and downgraded the underline into start of a new paragraph block. */
6351 line->type = MD_LINE_TEXT;
6352 *p_pivot_line = line;
6353 }
6354 return 0;
6355 }
6356
6357 /* MD_LINE_TABLEUNDERLINE changes meaning of the current block. */
6358 if(line->type == MD_LINE_TABLEUNDERLINE) {
6359 MD_ASSERT(ctx->current_block != NULL);
6360 MD_ASSERT(ctx->current_block->n_lines == 1);
6361 ctx->current_block->type = MD_BLOCK_TABLE;
6362 ctx->current_block->data = line->data;
6363 MD_ASSERT(pivot_line != &md_dummy_blank_line);
6364 ((MD_LINE_ANALYSIS*)pivot_line)->type = MD_LINE_TABLE;
6365 MD_CHECK(md_add_line_into_current_block(ctx, line));
6366 return 0;
6367 }
6368
6369 /* The current block also ends if the line has different type. */
6370 if(line->type != pivot_line->type)
6371 MD_CHECK(md_end_current_block(ctx));
6372
6373 /* The current line may start a new block. */
6374 if(ctx->current_block == NULL) {
6375 MD_CHECK(md_start_new_block(ctx, line));
6376 *p_pivot_line = line;
6377 }
6378
6379 /* In all other cases the line is just a continuation of the current block. */
6380 MD_CHECK(md_add_line_into_current_block(ctx, line));
6381
6382abort:
6383 return ret;
6384}
6385
6386static int
6387md_process_doc(MD_CTX *ctx)
6388{
6389 const MD_LINE_ANALYSIS* pivot_line = &md_dummy_blank_line;
6390 MD_LINE_ANALYSIS line_buf[2];
6391 MD_LINE_ANALYSIS* line = &line_buf[0];
6392 OFF off = 0;
6393 int ret = 0;
6394
6395 MD_ENTER_BLOCK(MD_BLOCK_DOC, NULL);
6396
6397 while(off < ctx->size) {
6398 if(line == pivot_line)
6399 line = (line == &line_buf[0] ? &line_buf[1] : &line_buf[0]);
6400
6401 MD_CHECK(md_analyze_line(ctx, off, &off, pivot_line, line));
6402 MD_CHECK(md_process_line(ctx, &pivot_line, line));
6403 }
6404
6405 md_end_current_block(ctx);
6406
6407 MD_CHECK(md_build_ref_def_hashtable(ctx));
6408
6409 /* Process all blocks. */
6410 MD_CHECK(md_leave_child_containers(ctx, 0));
6411 MD_CHECK(md_process_all_blocks(ctx));
6412
6413 MD_LEAVE_BLOCK(MD_BLOCK_DOC, NULL);
6414
6415abort:
6416
6417#if 0
6418 /* Output some memory consumption statistics. */
6419 {
6420 char buffer[256];
6421 sprintf(buffer, "Alloced %u bytes for block buffer.",
6422 (unsigned)(ctx->alloc_block_bytes));
6423 MD_LOG(buffer);
6424
6425 sprintf(buffer, "Alloced %u bytes for containers buffer.",
6426 (unsigned)(ctx->alloc_containers * sizeof(MD_CONTAINER)));
6427 MD_LOG(buffer);
6428
6429 sprintf(buffer, "Alloced %u bytes for marks buffer.",
6430 (unsigned)(ctx->alloc_marks * sizeof(MD_MARK)));
6431 MD_LOG(buffer);
6432
6433 sprintf(buffer, "Alloced %u bytes for aux. buffer.",
6434 (unsigned)(ctx->alloc_buffer * sizeof(MD_CHAR)));
6435 MD_LOG(buffer);
6436 }
6437#endif
6438
6439 return ret;
6440}
6441
6442
6443/********************
6444 *** Public API ***
6445 ********************/
6446
6447int
6448md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userdata)
6449{
6450 MD_CTX ctx;
6451 int i;
6452 int ret;
6453
6454 if(parser->abi_version != 0) {
6455 if(parser->debug_log != NULL)
6456 parser->debug_log("Unsupported abi_version.", userdata);
6457 return -1;
6458 }
6459
6460 /* Setup context structure. */
6461 memset(&ctx, 0, sizeof(MD_CTX));
6462 ctx.text = text;
6463 ctx.size = size;
6464 memcpy(&ctx.parser, parser, sizeof(MD_PARSER));
6465 ctx.userdata = userdata;
6466 ctx.code_indent_offset = (ctx.parser.flags & MD_FLAG_NOINDENTEDCODEBLOCKS) ? (OFF)(-1) : 4;
6467 md_build_mark_char_map(&ctx);
6468 ctx.doc_ends_with_newline = (size > 0 && ISNEWLINE_(text[size-1]));
6469 ctx.max_ref_def_output = MIN(MIN(16 * (uint64_t)size, (uint64_t)(1024 * 1024)), (uint64_t)SZ_MAX);
6470
6471 /* Reset all mark stacks and lists. */
6472 for(i = 0; i < (int) SIZEOF_ARRAY(ctx.opener_stacks); i++)
6473 ctx.opener_stacks[i].top = -1;
6474 ctx.ptr_stack.top = -1;
6475 ctx.unresolved_link_head = -1;
6476 ctx.unresolved_link_tail = -1;
6477 ctx.table_cell_boundaries_head = -1;
6478 ctx.table_cell_boundaries_tail = -1;
6479
6480 /* All the work. */
6481 ret = md_process_doc(&ctx);
6482
6483 /* Clean-up. */
6484 md_free_ref_defs(&ctx);
6485 md_free_ref_def_hashtable(&ctx);
6486 free(ctx.buffer);
6487 free(ctx.marks);
6488 free(ctx.block_bytes);
6489 free(ctx.containers);
6490
6491 return ret;
6492}