md4c.c

   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}