md4c

C Markdown parser. Fast. SAX-like interface. Compliant to CommonMark specification.
git clone https://noulin.net/git/md4c.git
Log | Files | Refs | README | LICENSE

commit f814d89369829e5aabcbac0a059f972949c9ccd6
parent 2b860198f85803e820332d7e33ae268ae3553699
Author: Remy Noulin <loader2x@gmail.com>
Date:   Mon, 26 Dec 2022 20:19:07 +0100

add _ for underline and __ for bold instead of underline

md4c/md4c.c | 3622 +++++++++++++++++++++++++++++++++--------------------------
md4c/md4c.h |  148 ++-
2 files changed, 2167 insertions(+), 1603 deletions(-)

Diffstat:
Mmd4c/md4c.c | 3622+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mmd4c/md4c.h | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
2 files changed, 2167 insertions(+), 1603 deletions(-)

diff --git a/md4c/md4c.c b/md4c/md4c.c @@ -1,8 +1,8 @@ -/* +/* commit e9ff661ff818ee94a4a231958d9b6768dc6882c9 - added _ for underline and __ for bold instead of underline * MD4C: Markdown parser for C * (http://github.com/mity/md4c) * - * Copyright (c) 2016-2017 Martin Mitas + * Copyright (c) 2016-2020 Martin Mitas * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -25,6 +25,7 @@ #include "md4c.h" +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -34,14 +35,23 @@ *** Miscellaneous Stuff *** *****************************/ -#ifdef _MSC_VER - /* MSVC does not understand "inline" when building as pure C (not C++). - * However it understands "__inline" */ - #ifndef __cplusplus +#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199409L + /* C89/90 or old compilers in general may not understand "inline". */ + #if defined __GNUC__ + #define inline __inline__ + #elif defined _MSC_VER #define inline __inline + #else + #define inline #endif #endif +/* Make the UTF-8 support the default. */ +#if !defined MD4C_USE_ASCII && !defined MD4C_USE_UTF8 && !defined MD4C_USE_UTF16 + #define MD4C_USE_UTF8 +#endif + +/* Magic for making wide literals with MD4C_USE_UTF16. */ #ifdef _T #undef _T #endif @@ -62,6 +72,48 @@ #define FALSE 0 #endif +#define MD_LOG(msg) \ + do { \ + if(ctx->parser.debug_log != NULL) \ + ctx->parser.debug_log((msg), ctx->userdata); \ + } while(0) + +#ifdef DEBUG + #define MD_ASSERT(cond) \ + do { \ + if(!(cond)) { \ + MD_LOG(__FILE__ ":" STRINGIZE(__LINE__) ": " \ + "Assertion '" STRINGIZE(cond) "' failed."); \ + exit(1); \ + } \ + } while(0) + + #define MD_UNREACHABLE() MD_ASSERT(1 == 0) +#else + #ifdef __GNUC__ + #define MD_ASSERT(cond) do { if(!(cond)) __builtin_unreachable(); } while(0) + #define MD_UNREACHABLE() do { __builtin_unreachable(); } while(0) + #elif defined _MSC_VER && _MSC_VER > 120 + #define MD_ASSERT(cond) do { __assume(cond); } while(0) + #define MD_UNREACHABLE() do { __assume(0); } while(0) + #else + #define MD_ASSERT(cond) do {} while(0) + #define MD_UNREACHABLE() do {} while(0) + #endif +#endif + +/* For falling through case labels in switch statements. */ +#if defined __clang__ && __clang_major__ >= 12 + #define MD_FALLTHROUGH() __attribute__((fallthrough)) +#elif defined __GNUC__ && __GNUC__ >= 7 + #define MD_FALLTHROUGH() __attribute__((fallthrough)) +#else + #define MD_FALLTHROUGH() ((void)0) +#endif + +/* Suppress "unused parameter" warnings. */ +#define MD_UNUSED(x) ((void)x) + /************************ *** Internal Types *** @@ -94,9 +146,12 @@ struct MD_CTX_tag { /* Immutable stuff (parameters of md_parse()). */ const CHAR* text; SZ size; - MD_RENDERER r; + MD_PARSER parser; void* userdata; + /* When this is true, it allows some optimizations. */ + int doc_ends_with_newline; + /* Helper temporary growing buffer. */ CHAR* buffer; unsigned alloc_buffer; @@ -123,15 +178,22 @@ struct MD_CTX_tag { #endif /* For resolving of inline spans. */ - MD_MARKCHAIN mark_chains[8]; -#define PTR_CHAIN ctx->mark_chains[0] -#define BACKTICK_OPENERS ctx->mark_chains[1] -#define LOWERTHEN_OPENERS ctx->mark_chains[2] -#define ASTERISK_OPENERS ctx->mark_chains[3] -#define UNDERSCORE_OPENERS ctx->mark_chains[4] -#define TILDE_OPENERS ctx->mark_chains[5] -#define BRACKET_OPENERS ctx->mark_chains[6] -#define TABLECELLBOUNDARIES ctx->mark_chains[7] + MD_MARKCHAIN mark_chains[13]; +#define PTR_CHAIN (ctx->mark_chains[0]) +#define TABLECELLBOUNDARIES (ctx->mark_chains[1]) +#define ASTERISK_OPENERS_extraword_mod3_0 (ctx->mark_chains[2]) +#define ASTERISK_OPENERS_extraword_mod3_1 (ctx->mark_chains[3]) +#define ASTERISK_OPENERS_extraword_mod3_2 (ctx->mark_chains[4]) +#define ASTERISK_OPENERS_intraword_mod3_0 (ctx->mark_chains[5]) +#define ASTERISK_OPENERS_intraword_mod3_1 (ctx->mark_chains[6]) +#define ASTERISK_OPENERS_intraword_mod3_2 (ctx->mark_chains[7]) +#define UNDERSCORE_OPENERS (ctx->mark_chains[8]) +#define TILDE_OPENERS_1 (ctx->mark_chains[9]) +#define TILDE_OPENERS_2 (ctx->mark_chains[10]) +#define BRACKET_OPENERS (ctx->mark_chains[11]) +#define DOLLAR_OPENERS (ctx->mark_chains[12]) +#define OPENERS_CHAIN_FIRST 1 +#define OPENERS_CHAIN_LAST 12 int n_table_cell_boundaries; @@ -139,6 +201,12 @@ struct MD_CTX_tag { int unresolved_link_head; int unresolved_link_tail; + /* For resolving raw HTML. */ + OFF html_comment_horizon; + OFF html_proc_instr_horizon; + OFF html_decl_horizon; + OFF html_cdata_horizon; + /* For block analysis. * Notes: * -- It holds MD_BLOCK as well as MD_LINE structures. After each @@ -156,18 +224,16 @@ struct MD_CTX_tag { int n_containers; int alloc_containers; - int last_line_has_list_loosening_effect; - int last_list_item_starts_with_two_blank_lines; - /* Minimal indentation to call the block "indented code block". */ unsigned code_indent_offset; /* Contextual info for line analysis. */ SZ code_fence_length; /* For checking closing fence length. */ int html_block_type; /* For checking closing raw HTML condition. */ + int last_line_has_list_loosening_effect; + int last_list_item_starts_with_two_blank_lines; }; -typedef enum MD_LINETYPE_tag MD_LINETYPE; enum MD_LINETYPE_tag { MD_LINE_BLANK, MD_LINE_HR, @@ -181,6 +247,7 @@ enum MD_LINETYPE_tag { MD_LINE_TABLE, MD_LINE_TABLEUNDERLINE }; +typedef enum MD_LINETYPE_tag MD_LINETYPE; typedef struct MD_LINE_ANALYSIS_tag MD_LINE_ANALYSIS; struct MD_LINE_ANALYSIS_tag { @@ -205,41 +272,6 @@ struct MD_VERBATIMLINE_tag { }; -/******************* - *** Debugging *** - *******************/ - -#define MD_LOG(msg) \ - do { \ - if(ctx->r.debug_log != NULL) \ - ctx->r.debug_log((msg), ctx->userdata); \ - } while(0) - -#ifdef DEBUG - #define MD_ASSERT(cond) \ - do { \ - if(!(cond)) { \ - MD_LOG(__FILE__ ":" STRINGIZE(__LINE__) ": " \ - "Assertion '" STRINGIZE(cond) "' failed."); \ - exit(1); \ - } \ - } while(0) - - #define MD_UNREACHABLE() MD_ASSERT(1 == 0) -#else - #ifdef __GNUC__ - #define MD_ASSERT(cond) do { if(!(cond)) __builtin_unreachable(); } while(0) - #define MD_UNREACHABLE() do { __builtin_unreachable(); } while(0) - #elif defined _MSC_VER && _MSC_VER > 120 - #define MD_ASSERT(cond) do { __assume(cond); } while(0) - #define MD_UNREACHABLE() do { __assume(0); } while(0) - #else - #define MD_ASSERT(cond) do {} while(0) - #define MD_UNREACHABLE() do {} while(0) - #endif -#endif - - /***************** *** Helpers *** *****************/ @@ -251,7 +283,7 @@ struct MD_VERBATIMLINE_tag { /* Character classification. * Note we assume ASCII compatibility of code points < 128 here. */ #define ISIN_(ch, ch_min, ch_max) ((ch_min) <= (unsigned)(ch) && (unsigned)(ch) <= (ch_max)) -#define ISANYOF_(ch, palette) (md_strchr((palette), (ch)) != NULL) +#define ISANYOF_(ch, palette) ((ch) != _T('\0') && md_strchr((palette), (ch)) != NULL) #define ISANYOF2_(ch, ch1, ch2) ((ch) == (ch1) || (ch) == (ch2)) #define ISANYOF3_(ch, ch1, ch2, ch3) ((ch) == (ch1) || (ch) == (ch2) || (ch) == (ch3)) #define ISASCII_(ch) ((unsigned)(ch) <= 127) @@ -282,16 +314,14 @@ struct MD_VERBATIMLINE_tag { #define ISDIGIT(off) ISDIGIT_(CH(off)) #define ISXDIGIT(off) ISXDIGIT_(CH(off)) #define ISALNUM(off) ISALNUM_(CH(off)) -static inline const CHAR* -md_strchr(const CHAR* str, CHAR ch) -{ - OFF i; - for(i = 0; str[i] != _T('\0'); i++) { - if(ch == str[i]) - return (str + i); - } - return NULL; -} + + +#if defined MD4C_USE_UTF16 + #define md_strchr wcschr +#else + #define md_strchr strchr +#endif + /* Case insensitive check of string equality. */ static inline int @@ -329,7 +359,7 @@ md_text_with_null_replacement(MD_CTX* ctx, MD_TEXTTYPE type, const CHAR* str, SZ off++; if(off > 0) { - ret = ctx->r.text(type, str, off, ctx->userdata); + ret = ctx->parser.text(type, str, off, ctx->userdata); if(ret != 0) return ret; @@ -341,7 +371,7 @@ md_text_with_null_replacement(MD_CTX* ctx, MD_TEXTTYPE type, const CHAR* str, SZ if(off >= size) return 0; - ret = ctx->r.text(MD_TEXT_NULLCHAR, _T(""), 1, ctx->userdata); + ret = ctx->parser.text(MD_TEXT_NULLCHAR, _T(""), 1, ctx->userdata); if(ret != 0) return ret; off++; @@ -349,92 +379,121 @@ md_text_with_null_replacement(MD_CTX* ctx, MD_TEXTTYPE type, const CHAR* str, SZ } -#define MD_CHECK(func) \ - do { \ - ret = (func); \ - if(ret < 0) \ - goto abort; \ +#define MD_CHECK(func) \ + do { \ + ret = (func); \ + if(ret < 0) \ + goto abort; \ } while(0) -#define MD_TEMP_BUFFER(sz) \ - do { \ - if(sz > ctx->alloc_buffer) { \ - CHAR* new_buffer; \ - SZ new_size = ((sz) + (sz) / 2 + 128) & ~127; \ - \ - new_buffer = realloc(ctx->buffer, new_size); \ - if(new_buffer == NULL) { \ - MD_LOG("realloc() failed."); \ - ret = -1; \ - goto abort; \ - } \ - \ - ctx->buffer = new_buffer; \ - ctx->alloc_buffer = new_size; \ - } \ +#define MD_TEMP_BUFFER(sz) \ + do { \ + if(sz > ctx->alloc_buffer) { \ + CHAR* new_buffer; \ + SZ new_size = ((sz) + (sz) / 2 + 128) & ~127; \ + \ + new_buffer = realloc(ctx->buffer, new_size); \ + if(new_buffer == NULL) { \ + MD_LOG("realloc() failed."); \ + ret = -1; \ + goto abort; \ + } \ + \ + ctx->buffer = new_buffer; \ + ctx->alloc_buffer = new_size; \ + } \ } while(0) -#define MD_ENTER_BLOCK(type, arg) \ - do { \ - ret = ctx->r.enter_block((type), (arg), ctx->userdata); \ - if(ret != 0) { \ - MD_LOG("Aborted from enter_block() callback."); \ - goto abort; \ - } \ +#define MD_ENTER_BLOCK(type, arg) \ + do { \ + ret = ctx->parser.enter_block((type), (arg), ctx->userdata); \ + if(ret != 0) { \ + MD_LOG("Aborted from enter_block() callback."); \ + goto abort; \ + } \ } while(0) -#define MD_LEAVE_BLOCK(type, arg) \ - do { \ - ret = ctx->r.leave_block((type), (arg), ctx->userdata); \ - if(ret != 0) { \ - MD_LOG("Aborted from leave_block() callback."); \ - goto abort; \ - } \ +#define MD_LEAVE_BLOCK(type, arg) \ + do { \ + ret = ctx->parser.leave_block((type), (arg), ctx->userdata); \ + if(ret != 0) { \ + MD_LOG("Aborted from leave_block() callback."); \ + goto abort; \ + } \ } while(0) -#define MD_ENTER_SPAN(type, arg) \ - do { \ - ret = ctx->r.enter_span((type), (arg), ctx->userdata); \ - if(ret != 0) { \ - MD_LOG("Aborted from enter_span() callback."); \ - goto abort; \ - } \ +#define MD_ENTER_SPAN(type, arg) \ + do { \ + ret = ctx->parser.enter_span((type), (arg), ctx->userdata); \ + if(ret != 0) { \ + MD_LOG("Aborted from enter_span() callback."); \ + goto abort; \ + } \ } while(0) -#define MD_LEAVE_SPAN(type, arg) \ - do { \ - ret = ctx->r.leave_span((type), (arg), ctx->userdata); \ - if(ret != 0) { \ - MD_LOG("Aborted from leave_span() callback."); \ - goto abort; \ - } \ +#define MD_LEAVE_SPAN(type, arg) \ + do { \ + ret = ctx->parser.leave_span((type), (arg), ctx->userdata); \ + if(ret != 0) { \ + MD_LOG("Aborted from leave_span() callback."); \ + goto abort; \ + } \ } while(0) -#define MD_TEXT(type, str, size) \ - do { \ - if(size > 0) { \ - ret = ctx->r.text((type), (str), (size), ctx->userdata); \ - if(ret != 0) { \ - MD_LOG("Aborted from text() callback."); \ - goto abort; \ - } \ - } \ +#define MD_TEXT(type, str, size) \ + do { \ + if(size > 0) { \ + ret = ctx->parser.text((type), (str), (size), ctx->userdata); \ + if(ret != 0) { \ + MD_LOG("Aborted from text() callback."); \ + goto abort; \ + } \ + } \ } while(0) -#define MD_TEXT_INSECURE(type, str, size) \ - do { \ - if(size > 0) { \ - ret = md_text_with_null_replacement(ctx, type, str, size); \ - if(ret != 0) { \ - MD_LOG("Aborted from text() callback."); \ - goto abort; \ - } \ - } \ +#define MD_TEXT_INSECURE(type, str, size) \ + do { \ + if(size > 0) { \ + ret = md_text_with_null_replacement(ctx, type, str, size); \ + if(ret != 0) { \ + MD_LOG("Aborted from text() callback."); \ + goto abort; \ + } \ + } \ } while(0) +/* If the offset falls into a gap between line, we return the following + * line. */ +static const MD_LINE* +md_lookup_line(OFF off, const MD_LINE* lines, int n_lines) +{ + int lo, hi; + int pivot; + const MD_LINE* line; + + lo = 0; + hi = n_lines - 1; + while(lo <= hi) { + pivot = (lo + hi) / 2; + line = &lines[pivot]; + + if(off < line->beg) { + hi = pivot - 1; + if(hi < 0 || lines[hi].end <= off) + return line; + } else if(off > line->end) { + lo = pivot + 1; + } else { + return line; + } + } + + return NULL; +} + /************************* *** Unicode Support *** @@ -442,209 +501,216 @@ md_text_with_null_replacement(MD_CTX* ctx, MD_TEXTTYPE type, const CHAR* str, SZ typedef struct MD_UNICODE_FOLD_INFO_tag MD_UNICODE_FOLD_INFO; struct MD_UNICODE_FOLD_INFO_tag { - int codepoints[3]; - int n_codepoints; + unsigned codepoints[3]; + unsigned n_codepoints; }; #if defined MD4C_USE_UTF16 || defined MD4C_USE_UTF8 + /* Binary search over sorted "map" of codepoints. Consecutive sequences + * of codepoints may be encoded in the map by just using the + * (MIN_CODEPOINT | 0x40000000) and (MAX_CODEPOINT | 0x80000000). + * + * Returns index of the found record in the map (in the case of ranges, + * the minimal value is used); or -1 on failure. */ static int - md_is_unicode_whitespace__(int codepoint) + md_unicode_bsearch__(unsigned codepoint, const unsigned* map, size_t map_size) { - /* The ASCII ones are the most frequently used ones, so lets check them first. */ - if(codepoint <= 0x7f) - return ISWHITESPACE_(codepoint); - - /* Check for Unicode codepoints in Zs class above 127. */ - if(codepoint == 0x00a0 || codepoint == 0x1680) - return TRUE; - if(0x2000 <= codepoint && codepoint <= 0x200a) - return TRUE; - if(codepoint == 0x202f || codepoint == 0x205f || codepoint == 0x3000) - return TRUE; + int beg, end; + int pivot_beg, pivot_end; + + beg = 0; + end = (int) map_size-1; + while(beg <= end) { + /* Pivot may be a range, not just a single value. */ + pivot_beg = pivot_end = (beg + end) / 2; + if(map[pivot_end] & 0x40000000) + pivot_end++; + if(map[pivot_beg] & 0x80000000) + pivot_beg--; + + if(codepoint < (map[pivot_beg] & 0x00ffffff)) + end = pivot_beg - 1; + else if(codepoint > (map[pivot_end] & 0x00ffffff)) + beg = pivot_end + 1; + else + return pivot_beg; + } - return FALSE; + return -1; } static int - md_unicode_cmp__(const void* p_codepoint_a, const void* p_codepoint_b) + md_is_unicode_whitespace__(unsigned codepoint) { - return (*(const int*)p_codepoint_a - *(const int*)p_codepoint_b); +#define R(cp_min, cp_max) ((cp_min) | 0x40000000), ((cp_max) | 0x80000000) +#define S(cp) (cp) + /* Unicode "Zs" category. + * (generated by scripts/build_whitespace_map.py) */ + static const unsigned WHITESPACE_MAP[] = { + S(0x0020), S(0x00a0), S(0x1680), R(0x2000,0x200a), S(0x202f), S(0x205f), S(0x3000) + }; +#undef R +#undef S + + /* The ASCII ones are the most frequently used ones, also CommonMark + * specification requests few more in this range. */ + if(codepoint <= 0x7f) + return ISWHITESPACE_(codepoint); + + return (md_unicode_bsearch__(codepoint, WHITESPACE_MAP, SIZEOF_ARRAY(WHITESPACE_MAP)) >= 0); } static int - md_is_unicode_punct__(int codepoint) + md_is_unicode_punct__(unsigned codepoint) { - /* non-ASCII (above 127) Unicode punctuation codepoints (classes - * Pc, Pd, Pe, Pf, Pi, Po, Ps). - * - * Warning: Keep the array sorted. - */ - static const int punct_list[] = { - 0x00a1, 0x00a7, 0x00ab, 0x00b6, 0x00b7, 0x00bb, 0x00bf, 0x037e, 0x0387, 0x055a, 0x055b, 0x055c, 0x055d, 0x055e, 0x055f, 0x0589, - 0x058a, 0x05be, 0x05c0, 0x05c3, 0x05c6, 0x05f3, 0x05f4, 0x0609, 0x060a, 0x060c, 0x060d, 0x061b, 0x061e, 0x061f, 0x066a, 0x066b, - 0x066c, 0x066d, 0x06d4, 0x0700, 0x0701, 0x0702, 0x0703, 0x0704, 0x0705, 0x0706, 0x0707, 0x0708, 0x0709, 0x070a, 0x070b, 0x070c, - 0x070d, 0x07f7, 0x07f8, 0x07f9, 0x0830, 0x0831, 0x0832, 0x0833, 0x0834, 0x0835, 0x0836, 0x0837, 0x0838, 0x0839, 0x083a, 0x083b, - 0x083c, 0x083d, 0x083e, 0x085e, 0x0964, 0x0965, 0x0970, 0x0af0, 0x0df4, 0x0e4f, 0x0e5a, 0x0e5b, 0x0f04, 0x0f05, 0x0f06, 0x0f07, - 0x0f08, 0x0f09, 0x0f0a, 0x0f0b, 0x0f0c, 0x0f0d, 0x0f0e, 0x0f0f, 0x0f10, 0x0f11, 0x0f12, 0x0f14, 0x0f3a, 0x0f3b, 0x0f3c, 0x0f3d, - 0x0f85, 0x0fd0, 0x0fd1, 0x0fd2, 0x0fd3, 0x0fd4, 0x0fd9, 0x0fda, 0x104a, 0x104b, 0x104c, 0x104d, 0x104e, 0x104f, 0x10fb, 0x1360, - 0x1361, 0x1362, 0x1363, 0x1364, 0x1365, 0x1366, 0x1367, 0x1368, 0x1400, 0x166d, 0x166e, 0x169b, 0x169c, 0x16eb, 0x16ec, 0x16ed, - 0x1735, 0x1736, 0x17d4, 0x17d5, 0x17d6, 0x17d8, 0x17d9, 0x17da, 0x1800, 0x1801, 0x1802, 0x1803, 0x1804, 0x1805, 0x1806, 0x1807, - 0x1808, 0x1809, 0x180a, 0x1944, 0x1945, 0x1a1e, 0x1a1f, 0x1aa0, 0x1aa1, 0x1aa2, 0x1aa3, 0x1aa4, 0x1aa5, 0x1aa6, 0x1aa8, 0x1aa9, - 0x1aaa, 0x1aab, 0x1aac, 0x1aad, 0x1b5a, 0x1b5b, 0x1b5c, 0x1b5d, 0x1b5e, 0x1b5f, 0x1b60, 0x1bfc, 0x1bfd, 0x1bfe, 0x1bff, 0x1c3b, - 0x1c3c, 0x1c3d, 0x1c3e, 0x1c3f, 0x1c7e, 0x1c7f, 0x1cc0, 0x1cc1, 0x1cc2, 0x1cc3, 0x1cc4, 0x1cc5, 0x1cc6, 0x1cc7, 0x1cd3, 0x2010, - 0x2011, 0x2012, 0x2013, 0x2014, 0x2015, 0x2016, 0x2017, 0x2018, 0x2019, 0x201a, 0x201b, 0x201c, 0x201d, 0x201e, 0x201f, 0x2020, - 0x2021, 0x2022, 0x2023, 0x2024, 0x2025, 0x2026, 0x2027, 0x2030, 0x2031, 0x2032, 0x2033, 0x2034, 0x2035, 0x2036, 0x2037, 0x2038, - 0x2039, 0x203a, 0x203b, 0x203c, 0x203d, 0x203e, 0x203f, 0x2040, 0x2041, 0x2042, 0x2043, 0x2045, 0x2046, 0x2047, 0x2048, 0x2049, - 0x204a, 0x204b, 0x204c, 0x204d, 0x204e, 0x204f, 0x2050, 0x2051, 0x2053, 0x2054, 0x2055, 0x2056, 0x2057, 0x2058, 0x2059, 0x205a, - 0x205b, 0x205c, 0x205d, 0x205e, 0x207d, 0x207e, 0x208d, 0x208e, 0x2308, 0x2309, 0x230a, 0x230b, 0x2329, 0x232a, 0x2768, 0x2769, - 0x276a, 0x276b, 0x276c, 0x276d, 0x276e, 0x276f, 0x2770, 0x2771, 0x2772, 0x2773, 0x2774, 0x2775, 0x27c5, 0x27c6, 0x27e6, 0x27e7, - 0x27e8, 0x27e9, 0x27ea, 0x27eb, 0x27ec, 0x27ed, 0x27ee, 0x27ef, 0x2983, 0x2984, 0x2985, 0x2986, 0x2987, 0x2988, 0x2989, 0x298a, - 0x298b, 0x298c, 0x298d, 0x298e, 0x298f, 0x2990, 0x2991, 0x2992, 0x2993, 0x2994, 0x2995, 0x2996, 0x2997, 0x2998, 0x29d8, 0x29d9, - 0x29da, 0x29db, 0x29fc, 0x29fd, 0x2cf9, 0x2cfa, 0x2cfb, 0x2cfc, 0x2cfe, 0x2cff, 0x2d70, 0x2e00, 0x2e01, 0x2e02, 0x2e03, 0x2e04, - 0x2e05, 0x2e06, 0x2e07, 0x2e08, 0x2e09, 0x2e0a, 0x2e0b, 0x2e0c, 0x2e0d, 0x2e0e, 0x2e0f, 0x2e10, 0x2e11, 0x2e12, 0x2e13, 0x2e14, - 0x2e15, 0x2e16, 0x2e17, 0x2e18, 0x2e19, 0x2e1a, 0x2e1b, 0x2e1c, 0x2e1d, 0x2e1e, 0x2e1f, 0x2e20, 0x2e21, 0x2e22, 0x2e23, 0x2e24, - 0x2e25, 0x2e26, 0x2e27, 0x2e28, 0x2e29, 0x2e2a, 0x2e2b, 0x2e2c, 0x2e2d, 0x2e2e, 0x2e30, 0x2e31, 0x2e32, 0x2e33, 0x2e34, 0x2e35, - 0x2e36, 0x2e37, 0x2e38, 0x2e39, 0x2e3a, 0x2e3b, 0x2e3c, 0x2e3d, 0x2e3e, 0x2e3f, 0x2e40, 0x2e41, 0x2e42, 0x2e43, 0x2e44, 0x3001, - 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x300c, 0x300d, 0x300e, 0x300f, 0x3010, 0x3011, 0x3014, 0x3015, 0x3016, 0x3017, - 0x3018, 0x3019, 0x301a, 0x301b, 0x301c, 0x301d, 0x301e, 0x301f, 0x3030, 0x303d, 0x30a0, 0x30fb, 0xa4fe, 0xa4ff, 0xa60d, 0xa60e, - 0xa60f, 0xa673, 0xa67e, 0xa6f2, 0xa6f3, 0xa6f4, 0xa6f5, 0xa6f6, 0xa6f7, 0xa874, 0xa875, 0xa876, 0xa877, 0xa8ce, 0xa8cf, 0xa8f8, - 0xa8f9, 0xa8fa, 0xa8fc, 0xa92e, 0xa92f, 0xa95f, 0xa9c1, 0xa9c2, 0xa9c3, 0xa9c4, 0xa9c5, 0xa9c6, 0xa9c7, 0xa9c8, 0xa9c9, 0xa9ca, - 0xa9cb, 0xa9cc, 0xa9cd, 0xa9de, 0xa9df, 0xaa5c, 0xaa5d, 0xaa5e, 0xaa5f, 0xaade, 0xaadf, 0xaaf0, 0xaaf1, 0xabeb, 0xfd3e, 0xfd3f, - 0xfe10, 0xfe11, 0xfe12, 0xfe13, 0xfe14, 0xfe15, 0xfe16, 0xfe17, 0xfe18, 0xfe19, 0xfe30, 0xfe31, 0xfe32, 0xfe33, 0xfe34, 0xfe35, - 0xfe36, 0xfe37, 0xfe38, 0xfe39, 0xfe3a, 0xfe3b, 0xfe3c, 0xfe3d, 0xfe3e, 0xfe3f, 0xfe40, 0xfe41, 0xfe42, 0xfe43, 0xfe44, 0xfe45, - 0xfe46, 0xfe47, 0xfe48, 0xfe49, 0xfe4a, 0xfe4b, 0xfe4c, 0xfe4d, 0xfe4e, 0xfe4f, 0xfe50, 0xfe51, 0xfe52, 0xfe54, 0xfe55, 0xfe56, - 0xfe57, 0xfe58, 0xfe59, 0xfe5a, 0xfe5b, 0xfe5c, 0xfe5d, 0xfe5e, 0xfe5f, 0xfe60, 0xfe61, 0xfe63, 0xfe68, 0xfe6a, 0xfe6b, 0xff01, - 0xff02, 0xff03, 0xff05, 0xff06, 0xff07, 0xff08, 0xff09, 0xff0a, 0xff0c, 0xff0d, 0xff0e, 0xff0f, 0xff1a, 0xff1b, 0xff1f, 0xff20, - 0xff3b, 0xff3c, 0xff3d, 0xff3f, 0xff5b, 0xff5d, 0xff5f, 0xff60, 0xff61, 0xff62, 0xff63, 0xff64, 0xff65, 0x10100, 0x10101, 0x10102, - 0x1039f, 0x103d0, 0x1056f, 0x10857, 0x1091f, 0x1093f, 0x10a50, 0x10a51, 0x10a52, 0x10a53, 0x10a54, 0x10a55, 0x10a56, 0x10a57, 0x10a58, 0x10a7f, - 0x10af0, 0x10af1, 0x10af2, 0x10af3, 0x10af4, 0x10af5, 0x10af6, 0x10b39, 0x10b3a, 0x10b3b, 0x10b3c, 0x10b3d, 0x10b3e, 0x10b3f, 0x10b99, 0x10b9a, - 0x10b9b, 0x10b9c, 0x11047, 0x11048, 0x11049, 0x1104a, 0x1104b, 0x1104c, 0x1104d, 0x110bb, 0x110bc, 0x110be, 0x110bf, 0x110c0, 0x110c1, 0x11140, - 0x11141, 0x11142, 0x11143, 0x11174, 0x11175, 0x111c5, 0x111c6, 0x111c7, 0x111c8, 0x111c9, 0x111cd, 0x111db, 0x111dd, 0x111de, 0x111df, 0x11238, - 0x11239, 0x1123a, 0x1123b, 0x1123c, 0x1123d, 0x112a9, 0x1144b, 0x1144c, 0x1144d, 0x1144e, 0x1144f, 0x1145b, 0x1145d, 0x114c6, 0x115c1, 0x115c2, - 0x115c3, 0x115c4, 0x115c5, 0x115c6, 0x115c7, 0x115c8, 0x115c9, 0x115ca, 0x115cb, 0x115cc, 0x115cd, 0x115ce, 0x115cf, 0x115d0, 0x115d1, 0x115d2, - 0x115d3, 0x115d4, 0x115d5, 0x115d6, 0x115d7, 0x11641, 0x11642, 0x11643, 0x11660, 0x11661, 0x11662, 0x11663, 0x11664, 0x11665, 0x11666, 0x11667, - 0x11668, 0x11669, 0x1166a, 0x1166b, 0x1166c, 0x1173c, 0x1173d, 0x1173e, 0x11c41, 0x11c42, 0x11c43, 0x11c44, 0x11c45, 0x11c70, 0x11c71, 0x12470, - 0x12471, 0x12472, 0x12473, 0x12474, 0x16a6e, 0x16a6f, 0x16af5, 0x16b37, 0x16b38, 0x16b39, 0x16b3a, 0x16b3b, 0x16b44, 0x1bc9f, 0x1da87, 0x1da88, - 0x1da89, 0x1da8a, 0x1da8b, 0x1e95e, 0x1e95f +#define R(cp_min, cp_max) ((cp_min) | 0x40000000), ((cp_max) | 0x80000000) +#define S(cp) (cp) + /* Unicode "Pc", "Pd", "Pe", "Pf", "Pi", "Po", "Ps" categories. + * (generated by scripts/build_punct_map.py) */ + static const unsigned PUNCT_MAP[] = { + R(0x0021,0x0023), R(0x0025,0x002a), R(0x002c,0x002f), R(0x003a,0x003b), R(0x003f,0x0040), + R(0x005b,0x005d), S(0x005f), S(0x007b), S(0x007d), S(0x00a1), S(0x00a7), S(0x00ab), R(0x00b6,0x00b7), + S(0x00bb), S(0x00bf), S(0x037e), S(0x0387), R(0x055a,0x055f), R(0x0589,0x058a), S(0x05be), S(0x05c0), + S(0x05c3), S(0x05c6), R(0x05f3,0x05f4), R(0x0609,0x060a), R(0x060c,0x060d), S(0x061b), R(0x061e,0x061f), + R(0x066a,0x066d), S(0x06d4), R(0x0700,0x070d), R(0x07f7,0x07f9), R(0x0830,0x083e), S(0x085e), + R(0x0964,0x0965), S(0x0970), S(0x09fd), S(0x0a76), S(0x0af0), S(0x0c77), S(0x0c84), S(0x0df4), S(0x0e4f), + R(0x0e5a,0x0e5b), R(0x0f04,0x0f12), S(0x0f14), R(0x0f3a,0x0f3d), S(0x0f85), R(0x0fd0,0x0fd4), + R(0x0fd9,0x0fda), R(0x104a,0x104f), S(0x10fb), R(0x1360,0x1368), S(0x1400), S(0x166e), R(0x169b,0x169c), + R(0x16eb,0x16ed), R(0x1735,0x1736), R(0x17d4,0x17d6), R(0x17d8,0x17da), R(0x1800,0x180a), + R(0x1944,0x1945), R(0x1a1e,0x1a1f), R(0x1aa0,0x1aa6), R(0x1aa8,0x1aad), R(0x1b5a,0x1b60), + R(0x1bfc,0x1bff), R(0x1c3b,0x1c3f), R(0x1c7e,0x1c7f), R(0x1cc0,0x1cc7), S(0x1cd3), R(0x2010,0x2027), + R(0x2030,0x2043), R(0x2045,0x2051), R(0x2053,0x205e), R(0x207d,0x207e), R(0x208d,0x208e), + R(0x2308,0x230b), R(0x2329,0x232a), R(0x2768,0x2775), R(0x27c5,0x27c6), R(0x27e6,0x27ef), + R(0x2983,0x2998), R(0x29d8,0x29db), R(0x29fc,0x29fd), R(0x2cf9,0x2cfc), R(0x2cfe,0x2cff), S(0x2d70), + R(0x2e00,0x2e2e), R(0x2e30,0x2e4f), S(0x2e52), R(0x3001,0x3003), R(0x3008,0x3011), R(0x3014,0x301f), + S(0x3030), S(0x303d), S(0x30a0), S(0x30fb), R(0xa4fe,0xa4ff), R(0xa60d,0xa60f), S(0xa673), S(0xa67e), + R(0xa6f2,0xa6f7), R(0xa874,0xa877), R(0xa8ce,0xa8cf), R(0xa8f8,0xa8fa), S(0xa8fc), R(0xa92e,0xa92f), + S(0xa95f), R(0xa9c1,0xa9cd), R(0xa9de,0xa9df), R(0xaa5c,0xaa5f), R(0xaade,0xaadf), R(0xaaf0,0xaaf1), + S(0xabeb), R(0xfd3e,0xfd3f), R(0xfe10,0xfe19), R(0xfe30,0xfe52), R(0xfe54,0xfe61), S(0xfe63), S(0xfe68), + R(0xfe6a,0xfe6b), R(0xff01,0xff03), R(0xff05,0xff0a), R(0xff0c,0xff0f), R(0xff1a,0xff1b), + R(0xff1f,0xff20), R(0xff3b,0xff3d), S(0xff3f), S(0xff5b), S(0xff5d), R(0xff5f,0xff65), R(0x10100,0x10102), + S(0x1039f), S(0x103d0), S(0x1056f), S(0x10857), S(0x1091f), S(0x1093f), R(0x10a50,0x10a58), S(0x10a7f), + R(0x10af0,0x10af6), R(0x10b39,0x10b3f), R(0x10b99,0x10b9c), S(0x10ead), R(0x10f55,0x10f59), + R(0x11047,0x1104d), R(0x110bb,0x110bc), R(0x110be,0x110c1), R(0x11140,0x11143), R(0x11174,0x11175), + R(0x111c5,0x111c8), S(0x111cd), S(0x111db), R(0x111dd,0x111df), R(0x11238,0x1123d), S(0x112a9), + R(0x1144b,0x1144f), R(0x1145a,0x1145b), S(0x1145d), S(0x114c6), R(0x115c1,0x115d7), R(0x11641,0x11643), + R(0x11660,0x1166c), R(0x1173c,0x1173e), S(0x1183b), R(0x11944,0x11946), S(0x119e2), R(0x11a3f,0x11a46), + R(0x11a9a,0x11a9c), R(0x11a9e,0x11aa2), R(0x11c41,0x11c45), R(0x11c70,0x11c71), R(0x11ef7,0x11ef8), + S(0x11fff), R(0x12470,0x12474), R(0x16a6e,0x16a6f), S(0x16af5), R(0x16b37,0x16b3b), S(0x16b44), + R(0x16e97,0x16e9a), S(0x16fe2), S(0x1bc9f), R(0x1da87,0x1da8b), R(0x1e95e,0x1e95f) }; +#undef R +#undef S - /* The ASCII ones are the most frequently used ones, so lets check them first. */ + /* The ASCII ones are the most frequently used ones, also CommonMark + * specification requests few more in this range. */ if(codepoint <= 0x7f) return ISPUNCT_(codepoint); - return (bsearch(&codepoint, punct_list, SIZEOF_ARRAY(punct_list), sizeof(int), md_unicode_cmp__) != NULL); + return (md_unicode_bsearch__(codepoint, PUNCT_MAP, SIZEOF_ARRAY(PUNCT_MAP)) >= 0); } static void - md_get_unicode_fold_info(int codepoint, MD_UNICODE_FOLD_INFO* info) + md_get_unicode_fold_info(unsigned codepoint, MD_UNICODE_FOLD_INFO* info) { - /* This maps single codepoint within a range to a single codepoint - * within an offseted range. */ - static const struct { - int min_codepoint; - int max_codepoint; - int offset; - } range_map[] = { - { 0x00c0, 0x00d6, 32 }, { 0x00d8, 0x00de, 32 }, { 0x0388, 0x038a, 37 }, { 0x0391, 0x03a1, 32 }, { 0x03a3, 0x03ab, 32 }, { 0x0400, 0x040f, 80 }, - { 0x0410, 0x042f, 32 }, { 0x0531, 0x0556, 48 }, { 0x1f08, 0x1f0f, -8 }, { 0x1f18, 0x1f1d, -8 }, { 0x1f28, 0x1f2f, -8 }, { 0x1f38, 0x1f3f, -8 }, - { 0x1f48, 0x1f4d, -8 }, { 0x1f68, 0x1f6f, -8 }, { 0x1fc8, 0x1fcb, -86 }, { 0x2160, 0x216f, 16 }, { 0x24b6, 0x24cf, 26 }, { 0xff21, 0xff3a, 32 }, - { 0x10400, 0x10425, 40 } +#define R(cp_min, cp_max) ((cp_min) | 0x40000000), ((cp_max) | 0x80000000) +#define S(cp) (cp) + /* Unicode "Pc", "Pd", "Pe", "Pf", "Pi", "Po", "Ps" categories. + * (generated by scripts/build_folding_map.py) */ + static const unsigned FOLD_MAP_1[] = { + R(0x0041,0x005a), S(0x00b5), R(0x00c0,0x00d6), R(0x00d8,0x00de), R(0x0100,0x012e), R(0x0132,0x0136), + R(0x0139,0x0147), R(0x014a,0x0176), S(0x0178), R(0x0179,0x017d), S(0x017f), S(0x0181), S(0x0182), + S(0x0184), S(0x0186), S(0x0187), S(0x0189), S(0x018a), S(0x018b), S(0x018e), S(0x018f), S(0x0190), + S(0x0191), S(0x0193), S(0x0194), S(0x0196), S(0x0197), S(0x0198), S(0x019c), S(0x019d), S(0x019f), + R(0x01a0,0x01a4), S(0x01a6), S(0x01a7), S(0x01a9), S(0x01ac), S(0x01ae), S(0x01af), S(0x01b1), S(0x01b2), + S(0x01b3), S(0x01b5), S(0x01b7), S(0x01b8), S(0x01bc), S(0x01c4), S(0x01c5), S(0x01c7), S(0x01c8), + S(0x01ca), R(0x01cb,0x01db), R(0x01de,0x01ee), S(0x01f1), S(0x01f2), S(0x01f4), S(0x01f6), S(0x01f7), + R(0x01f8,0x021e), S(0x0220), R(0x0222,0x0232), S(0x023a), S(0x023b), S(0x023d), S(0x023e), S(0x0241), + S(0x0243), S(0x0244), S(0x0245), R(0x0246,0x024e), S(0x0345), S(0x0370), S(0x0372), S(0x0376), S(0x037f), + S(0x0386), R(0x0388,0x038a), S(0x038c), S(0x038e), S(0x038f), R(0x0391,0x03a1), R(0x03a3,0x03ab), + S(0x03c2), S(0x03cf), S(0x03d0), S(0x03d1), S(0x03d5), S(0x03d6), R(0x03d8,0x03ee), S(0x03f0), S(0x03f1), + S(0x03f4), S(0x03f5), S(0x03f7), S(0x03f9), S(0x03fa), R(0x03fd,0x03ff), R(0x0400,0x040f), + R(0x0410,0x042f), R(0x0460,0x0480), R(0x048a,0x04be), S(0x04c0), R(0x04c1,0x04cd), R(0x04d0,0x052e), + R(0x0531,0x0556), R(0x10a0,0x10c5), S(0x10c7), S(0x10cd), R(0x13f8,0x13fd), S(0x1c80), S(0x1c81), + S(0x1c82), S(0x1c83), S(0x1c84), S(0x1c85), S(0x1c86), S(0x1c87), S(0x1c88), R(0x1c90,0x1cba), + R(0x1cbd,0x1cbf), R(0x1e00,0x1e94), S(0x1e9b), R(0x1ea0,0x1efe), R(0x1f08,0x1f0f), R(0x1f18,0x1f1d), + R(0x1f28,0x1f2f), R(0x1f38,0x1f3f), R(0x1f48,0x1f4d), S(0x1f59), S(0x1f5b), S(0x1f5d), S(0x1f5f), + R(0x1f68,0x1f6f), S(0x1fb8), S(0x1fb9), S(0x1fba), S(0x1fbb), S(0x1fbe), R(0x1fc8,0x1fcb), S(0x1fd8), + S(0x1fd9), S(0x1fda), S(0x1fdb), S(0x1fe8), S(0x1fe9), S(0x1fea), S(0x1feb), S(0x1fec), S(0x1ff8), + S(0x1ff9), S(0x1ffa), S(0x1ffb), S(0x2126), S(0x212a), S(0x212b), S(0x2132), R(0x2160,0x216f), S(0x2183), + R(0x24b6,0x24cf), R(0x2c00,0x2c2e), S(0x2c60), S(0x2c62), S(0x2c63), S(0x2c64), R(0x2c67,0x2c6b), + S(0x2c6d), S(0x2c6e), S(0x2c6f), S(0x2c70), S(0x2c72), S(0x2c75), S(0x2c7e), S(0x2c7f), R(0x2c80,0x2ce2), + S(0x2ceb), S(0x2ced), S(0x2cf2), R(0xa640,0xa66c), R(0xa680,0xa69a), R(0xa722,0xa72e), R(0xa732,0xa76e), + S(0xa779), S(0xa77b), S(0xa77d), R(0xa77e,0xa786), S(0xa78b), S(0xa78d), S(0xa790), S(0xa792), + R(0xa796,0xa7a8), S(0xa7aa), S(0xa7ab), S(0xa7ac), S(0xa7ad), S(0xa7ae), S(0xa7b0), S(0xa7b1), S(0xa7b2), + S(0xa7b3), R(0xa7b4,0xa7be), S(0xa7c2), S(0xa7c4), S(0xa7c5), S(0xa7c6), S(0xa7c7), S(0xa7c9), S(0xa7f5), + R(0xab70,0xabbf), R(0xff21,0xff3a), R(0x10400,0x10427), R(0x104b0,0x104d3), R(0x10c80,0x10cb2), + R(0x118a0,0x118bf), R(0x16e40,0x16e5f), R(0x1e900,0x1e921) }; - - /* This maps single codepoint to another single codepoint. */ - static const struct { - int src_codepoint; - int dest_codepoint; - } single_map[] = { - { 0x00b5, 0x03bc }, { 0x0100, 0x0101 }, { 0x0102, 0x0103 }, { 0x0104, 0x0105 }, { 0x0106, 0x0107 }, { 0x0108, 0x0109 }, { 0x010a, 0x010b }, { 0x010c, 0x010d }, - { 0x010e, 0x010f }, { 0x0110, 0x0111 }, { 0x0112, 0x0113 }, { 0x0114, 0x0115 }, { 0x0116, 0x0117 }, { 0x0118, 0x0119 }, { 0x011a, 0x011b }, { 0x011c, 0x011d }, - { 0x011e, 0x011f }, { 0x0120, 0x0121 }, { 0x0122, 0x0123 }, { 0x0124, 0x0125 }, { 0x0126, 0x0127 }, { 0x0128, 0x0129 }, { 0x012a, 0x012b }, { 0x012c, 0x012d }, - { 0x012e, 0x012f }, { 0x0132, 0x0133 }, { 0x0134, 0x0135 }, { 0x0136, 0x0137 }, { 0x0139, 0x013a }, { 0x013b, 0x013c }, { 0x013d, 0x013e }, { 0x013f, 0x0140 }, - { 0x0141, 0x0142 }, { 0x0143, 0x0144 }, { 0x0145, 0x0146 }, { 0x0147, 0x0148 }, { 0x014a, 0x014b }, { 0x014c, 0x014d }, { 0x014e, 0x014f }, { 0x0150, 0x0151 }, - { 0x0152, 0x0153 }, { 0x0154, 0x0155 }, { 0x0156, 0x0157 }, { 0x0158, 0x0159 }, { 0x015a, 0x015b }, { 0x015c, 0x015d }, { 0x015e, 0x015f }, { 0x0160, 0x0161 }, - { 0x0162, 0x0163 }, { 0x0164, 0x0165 }, { 0x0166, 0x0167 }, { 0x0168, 0x0169 }, { 0x016a, 0x016b }, { 0x016c, 0x016d }, { 0x016e, 0x016f }, { 0x0170, 0x0171 }, - { 0x0172, 0x0173 }, { 0x0174, 0x0175 }, { 0x0176, 0x0177 }, { 0x0178, 0x00ff }, { 0x0179, 0x017a }, { 0x017b, 0x017c }, { 0x017d, 0x017e }, { 0x017f, 0x0073 }, - { 0x0181, 0x0253 }, { 0x0182, 0x0183 }, { 0x0184, 0x0185 }, { 0x0186, 0x0254 }, { 0x0187, 0x0188 }, { 0x0189, 0x0256 }, { 0x018a, 0x0257 }, { 0x018b, 0x018c }, - { 0x018e, 0x01dd }, { 0x018f, 0x0259 }, { 0x0190, 0x025b }, { 0x0191, 0x0192 }, { 0x0193, 0x0260 }, { 0x0194, 0x0263 }, { 0x0196, 0x0269 }, { 0x0197, 0x0268 }, - { 0x0198, 0x0199 }, { 0x019c, 0x026f }, { 0x019d, 0x0272 }, { 0x019f, 0x0275 }, { 0x01a0, 0x01a1 }, { 0x01a2, 0x01a3 }, { 0x01a4, 0x01a5 }, { 0x01a6, 0x0280 }, - { 0x01a7, 0x01a8 }, { 0x01a9, 0x0283 }, { 0x01ac, 0x01ad }, { 0x01ae, 0x0288 }, { 0x01af, 0x01b0 }, { 0x01b1, 0x028a }, { 0x01b2, 0x028b }, { 0x01b3, 0x01b4 }, - { 0x01b5, 0x01b6 }, { 0x01b7, 0x0292 }, { 0x01b8, 0x01b9 }, { 0x01bc, 0x01bd }, { 0x01c4, 0x01c6 }, { 0x01c5, 0x01c6 }, { 0x01c7, 0x01c9 }, { 0x01c8, 0x01c9 }, - { 0x01ca, 0x01cc }, { 0x01cb, 0x01cc }, { 0x01cd, 0x01ce }, { 0x01cf, 0x01d0 }, { 0x01d1, 0x01d2 }, { 0x01d3, 0x01d4 }, { 0x01d5, 0x01d6 }, { 0x01d7, 0x01d8 }, - { 0x01d9, 0x01da }, { 0x01db, 0x01dc }, { 0x01de, 0x01df }, { 0x01e0, 0x01e1 }, { 0x01e2, 0x01e3 }, { 0x01e4, 0x01e5 }, { 0x01e6, 0x01e7 }, { 0x01e8, 0x01e9 }, - { 0x01ea, 0x01eb }, { 0x01ec, 0x01ed }, { 0x01ee, 0x01ef }, { 0x01f1, 0x01f3 }, { 0x01f2, 0x01f3 }, { 0x01f4, 0x01f5 }, { 0x01f6, 0x0195 }, { 0x01f7, 0x01bf }, - { 0x01f8, 0x01f9 }, { 0x01fa, 0x01fb }, { 0x01fc, 0x01fd }, { 0x01fe, 0x01ff }, { 0x0200, 0x0201 }, { 0x0202, 0x0203 }, { 0x0204, 0x0205 }, { 0x0206, 0x0207 }, - { 0x0208, 0x0209 }, { 0x020a, 0x020b }, { 0x020c, 0x020d }, { 0x020e, 0x020f }, { 0x0210, 0x0211 }, { 0x0212, 0x0213 }, { 0x0214, 0x0215 }, { 0x0216, 0x0217 }, - { 0x0218, 0x0219 }, { 0x021a, 0x021b }, { 0x021c, 0x021d }, { 0x021e, 0x021f }, { 0x0220, 0x019e }, { 0x0222, 0x0223 }, { 0x0224, 0x0225 }, { 0x0226, 0x0227 }, - { 0x0228, 0x0229 }, { 0x022a, 0x022b }, { 0x022c, 0x022d }, { 0x022e, 0x022f }, { 0x0230, 0x0231 }, { 0x0232, 0x0233 }, { 0x0345, 0x03b9 }, { 0x0386, 0x03ac }, - { 0x038c, 0x03cc }, { 0x038e, 0x03cd }, { 0x038f, 0x03ce }, { 0x03c2, 0x03c3 }, { 0x03d0, 0x03b2 }, { 0x03d1, 0x03b8 }, { 0x03d5, 0x03c6 }, { 0x03d6, 0x03c0 }, - { 0x03d8, 0x03d9 }, { 0x03da, 0x03db }, { 0x03dc, 0x03dd }, { 0x03de, 0x03df }, { 0x03e0, 0x03e1 }, { 0x03e2, 0x03e3 }, { 0x03e4, 0x03e5 }, { 0x03e6, 0x03e7 }, - { 0x03e8, 0x03e9 }, { 0x03ea, 0x03eb }, { 0x03ec, 0x03ed }, { 0x03ee, 0x03ef }, { 0x03f0, 0x03ba }, { 0x03f1, 0x03c1 }, { 0x03f2, 0x03c3 }, { 0x03f4, 0x03b8 }, - { 0x03f5, 0x03b5 }, { 0x0460, 0x0461 }, { 0x0462, 0x0463 }, { 0x0464, 0x0465 }, { 0x0466, 0x0467 }, { 0x0468, 0x0469 }, { 0x046a, 0x046b }, { 0x046c, 0x046d }, - { 0x046e, 0x046f }, { 0x0470, 0x0471 }, { 0x0472, 0x0473 }, { 0x0474, 0x0475 }, { 0x0476, 0x0477 }, { 0x0478, 0x0479 }, { 0x047a, 0x047b }, { 0x047c, 0x047d }, - { 0x047e, 0x047f }, { 0x0480, 0x0481 }, { 0x048a, 0x048b }, { 0x048c, 0x048d }, { 0x048e, 0x048f }, { 0x0490, 0x0491 }, { 0x0492, 0x0493 }, { 0x0494, 0x0495 }, - { 0x0496, 0x0497 }, { 0x0498, 0x0499 }, { 0x049a, 0x049b }, { 0x049c, 0x049d }, { 0x049e, 0x049f }, { 0x04a0, 0x04a1 }, { 0x04a2, 0x04a3 }, { 0x04a4, 0x04a5 }, - { 0x04a6, 0x04a7 }, { 0x04a8, 0x04a9 }, { 0x04aa, 0x04ab }, { 0x04ac, 0x04ad }, { 0x04ae, 0x04af }, { 0x04b0, 0x04b1 }, { 0x04b2, 0x04b3 }, { 0x04b4, 0x04b5 }, - { 0x04b6, 0x04b7 }, { 0x04b8, 0x04b9 }, { 0x04ba, 0x04bb }, { 0x04bc, 0x04bd }, { 0x04be, 0x04bf }, { 0x04c1, 0x04c2 }, { 0x04c3, 0x04c4 }, { 0x04c5, 0x04c6 }, - { 0x04c7, 0x04c8 }, { 0x04c9, 0x04ca }, { 0x04cb, 0x04cc }, { 0x04cd, 0x04ce }, { 0x04d0, 0x04d1 }, { 0x04d2, 0x04d3 }, { 0x04d4, 0x04d5 }, { 0x04d6, 0x04d7 }, - { 0x04d8, 0x04d9 }, { 0x04da, 0x04db }, { 0x04dc, 0x04dd }, { 0x04de, 0x04df }, { 0x04e0, 0x04e1 }, { 0x04e2, 0x04e3 }, { 0x04e4, 0x04e5 }, { 0x04e6, 0x04e7 }, - { 0x04e8, 0x04e9 }, { 0x04ea, 0x04eb }, { 0x04ec, 0x04ed }, { 0x04ee, 0x04ef }, { 0x04f0, 0x04f1 }, { 0x04f2, 0x04f3 }, { 0x04f4, 0x04f5 }, { 0x04f8, 0x04f9 }, - { 0x0500, 0x0501 }, { 0x0502, 0x0503 }, { 0x0504, 0x0505 }, { 0x0506, 0x0507 }, { 0x0508, 0x0509 }, { 0x050a, 0x050b }, { 0x050c, 0x050d }, { 0x050e, 0x050f }, - { 0x1e00, 0x1e01 }, { 0x1e02, 0x1e03 }, { 0x1e04, 0x1e05 }, { 0x1e06, 0x1e07 }, { 0x1e08, 0x1e09 }, { 0x1e0a, 0x1e0b }, { 0x1e0c, 0x1e0d }, { 0x1e0e, 0x1e0f }, - { 0x1e10, 0x1e11 }, { 0x1e12, 0x1e13 }, { 0x1e14, 0x1e15 }, { 0x1e16, 0x1e17 }, { 0x1e18, 0x1e19 }, { 0x1e1a, 0x1e1b }, { 0x1e1c, 0x1e1d }, { 0x1e1e, 0x1e1f }, - { 0x1e20, 0x1e21 }, { 0x1e22, 0x1e23 }, { 0x1e24, 0x1e25 }, { 0x1e26, 0x1e27 }, { 0x1e28, 0x1e29 }, { 0x1e2a, 0x1e2b }, { 0x1e2c, 0x1e2d }, { 0x1e2e, 0x1e2f }, - { 0x1e30, 0x1e31 }, { 0x1e32, 0x1e33 }, { 0x1e34, 0x1e35 }, { 0x1e36, 0x1e37 }, { 0x1e38, 0x1e39 }, { 0x1e3a, 0x1e3b }, { 0x1e3c, 0x1e3d }, { 0x1e3e, 0x1e3f }, - { 0x1e40, 0x1e41 }, { 0x1e42, 0x1e43 }, { 0x1e44, 0x1e45 }, { 0x1e46, 0x1e47 }, { 0x1e48, 0x1e49 }, { 0x1e4a, 0x1e4b }, { 0x1e4c, 0x1e4d }, { 0x1e4e, 0x1e4f }, - { 0x1e50, 0x1e51 }, { 0x1e52, 0x1e53 }, { 0x1e54, 0x1e55 }, { 0x1e56, 0x1e57 }, { 0x1e58, 0x1e59 }, { 0x1e5a, 0x1e5b }, { 0x1e5c, 0x1e5d }, { 0x1e5e, 0x1e5f }, - { 0x1e60, 0x1e61 }, { 0x1e62, 0x1e63 }, { 0x1e64, 0x1e65 }, { 0x1e66, 0x1e67 }, { 0x1e68, 0x1e69 }, { 0x1e6a, 0x1e6b }, { 0x1e6c, 0x1e6d }, { 0x1e6e, 0x1e6f }, - { 0x1e70, 0x1e71 }, { 0x1e72, 0x1e73 }, { 0x1e74, 0x1e75 }, { 0x1e76, 0x1e77 }, { 0x1e78, 0x1e79 }, { 0x1e7a, 0x1e7b }, { 0x1e7c, 0x1e7d }, { 0x1e7e, 0x1e7f }, - { 0x1e80, 0x1e81 }, { 0x1e82, 0x1e83 }, { 0x1e84, 0x1e85 }, { 0x1e86, 0x1e87 }, { 0x1e88, 0x1e89 }, { 0x1e8a, 0x1e8b }, { 0x1e8c, 0x1e8d }, { 0x1e8e, 0x1e8f }, - { 0x1e90, 0x1e91 }, { 0x1e92, 0x1e93 }, { 0x1e94, 0x1e95 }, { 0x1e9b, 0x1e61 }, { 0x1ea0, 0x1ea1 }, { 0x1ea2, 0x1ea3 }, { 0x1ea4, 0x1ea5 }, { 0x1ea6, 0x1ea7 }, - { 0x1ea8, 0x1ea9 }, { 0x1eaa, 0x1eab }, { 0x1eac, 0x1ead }, { 0x1eae, 0x1eaf }, { 0x1eb0, 0x1eb1 }, { 0x1eb2, 0x1eb3 }, { 0x1eb4, 0x1eb5 }, { 0x1eb6, 0x1eb7 }, - { 0x1eb8, 0x1eb9 }, { 0x1eba, 0x1ebb }, { 0x1ebc, 0x1ebd }, { 0x1ebe, 0x1ebf }, { 0x1ec0, 0x1ec1 }, { 0x1ec2, 0x1ec3 }, { 0x1ec4, 0x1ec5 }, { 0x1ec6, 0x1ec7 }, - { 0x1ec8, 0x1ec9 }, { 0x1eca, 0x1ecb }, { 0x1ecc, 0x1ecd }, { 0x1ece, 0x1ecf }, { 0x1ed0, 0x1ed1 }, { 0x1ed2, 0x1ed3 }, { 0x1ed4, 0x1ed5 }, { 0x1ed6, 0x1ed7 }, - { 0x1ed8, 0x1ed9 }, { 0x1eda, 0x1edb }, { 0x1edc, 0x1edd }, { 0x1ede, 0x1edf }, { 0x1ee0, 0x1ee1 }, { 0x1ee2, 0x1ee3 }, { 0x1ee4, 0x1ee5 }, { 0x1ee6, 0x1ee7 }, - { 0x1ee8, 0x1ee9 }, { 0x1eea, 0x1eeb }, { 0x1eec, 0x1eed }, { 0x1eee, 0x1eef }, { 0x1ef0, 0x1ef1 }, { 0x1ef2, 0x1ef3 }, { 0x1ef4, 0x1ef5 }, { 0x1ef6, 0x1ef7 }, - { 0x1ef8, 0x1ef9 }, { 0x1f59, 0x1f51 }, { 0x1f5b, 0x1f53 }, { 0x1f5d, 0x1f55 }, { 0x1f5f, 0x1f57 }, { 0x1fb8, 0x1fb0 }, { 0x1fb9, 0x1fb1 }, { 0x1fba, 0x1f70 }, - { 0x1fbb, 0x1f71 }, { 0x1fbe, 0x03b9 }, { 0x1fd8, 0x1fd0 }, { 0x1fd9, 0x1fd1 }, { 0x1fda, 0x1f76 }, { 0x1fdb, 0x1f77 }, { 0x1fe8, 0x1fe0 }, { 0x1fe9, 0x1fe1 }, - { 0x1fea, 0x1f7a }, { 0x1feb, 0x1f7b }, { 0x1fec, 0x1fe5 }, { 0x1ff8, 0x1f78 }, { 0x1ff9, 0x1f79 }, { 0x1ffa, 0x1f7c }, { 0x1ffb, 0x1f7d }, { 0x2126, 0x03c9 }, - { 0x212a, 0x006b }, { 0x212b, 0x00e5 }, + static const unsigned FOLD_MAP_1_DATA[] = { + 0x0061, 0x007a, 0x03bc, 0x00e0, 0x00f6, 0x00f8, 0x00fe, 0x0101, 0x012f, 0x0133, 0x0137, 0x013a, 0x0148, + 0x014b, 0x0177, 0x00ff, 0x017a, 0x017e, 0x0073, 0x0253, 0x0183, 0x0185, 0x0254, 0x0188, 0x0256, 0x0257, + 0x018c, 0x01dd, 0x0259, 0x025b, 0x0192, 0x0260, 0x0263, 0x0269, 0x0268, 0x0199, 0x026f, 0x0272, 0x0275, + 0x01a1, 0x01a5, 0x0280, 0x01a8, 0x0283, 0x01ad, 0x0288, 0x01b0, 0x028a, 0x028b, 0x01b4, 0x01b6, 0x0292, + 0x01b9, 0x01bd, 0x01c6, 0x01c6, 0x01c9, 0x01c9, 0x01cc, 0x01cc, 0x01dc, 0x01df, 0x01ef, 0x01f3, 0x01f3, + 0x01f5, 0x0195, 0x01bf, 0x01f9, 0x021f, 0x019e, 0x0223, 0x0233, 0x2c65, 0x023c, 0x019a, 0x2c66, 0x0242, + 0x0180, 0x0289, 0x028c, 0x0247, 0x024f, 0x03b9, 0x0371, 0x0373, 0x0377, 0x03f3, 0x03ac, 0x03ad, 0x03af, + 0x03cc, 0x03cd, 0x03ce, 0x03b1, 0x03c1, 0x03c3, 0x03cb, 0x03c3, 0x03d7, 0x03b2, 0x03b8, 0x03c6, 0x03c0, + 0x03d9, 0x03ef, 0x03ba, 0x03c1, 0x03b8, 0x03b5, 0x03f8, 0x03f2, 0x03fb, 0x037b, 0x037d, 0x0450, 0x045f, + 0x0430, 0x044f, 0x0461, 0x0481, 0x048b, 0x04bf, 0x04cf, 0x04c2, 0x04ce, 0x04d1, 0x052f, 0x0561, 0x0586, + 0x2d00, 0x2d25, 0x2d27, 0x2d2d, 0x13f0, 0x13f5, 0x0432, 0x0434, 0x043e, 0x0441, 0x0442, 0x0442, 0x044a, + 0x0463, 0xa64b, 0x10d0, 0x10fa, 0x10fd, 0x10ff, 0x1e01, 0x1e95, 0x1e61, 0x1ea1, 0x1eff, 0x1f00, 0x1f07, + 0x1f10, 0x1f15, 0x1f20, 0x1f27, 0x1f30, 0x1f37, 0x1f40, 0x1f45, 0x1f51, 0x1f53, 0x1f55, 0x1f57, 0x1f60, + 0x1f67, 0x1fb0, 0x1fb1, 0x1f70, 0x1f71, 0x03b9, 0x1f72, 0x1f75, 0x1fd0, 0x1fd1, 0x1f76, 0x1f77, 0x1fe0, + 0x1fe1, 0x1f7a, 0x1f7b, 0x1fe5, 0x1f78, 0x1f79, 0x1f7c, 0x1f7d, 0x03c9, 0x006b, 0x00e5, 0x214e, 0x2170, + 0x217f, 0x2184, 0x24d0, 0x24e9, 0x2c30, 0x2c5e, 0x2c61, 0x026b, 0x1d7d, 0x027d, 0x2c68, 0x2c6c, 0x0251, + 0x0271, 0x0250, 0x0252, 0x2c73, 0x2c76, 0x023f, 0x0240, 0x2c81, 0x2ce3, 0x2cec, 0x2cee, 0x2cf3, 0xa641, + 0xa66d, 0xa681, 0xa69b, 0xa723, 0xa72f, 0xa733, 0xa76f, 0xa77a, 0xa77c, 0x1d79, 0xa77f, 0xa787, 0xa78c, + 0x0265, 0xa791, 0xa793, 0xa797, 0xa7a9, 0x0266, 0x025c, 0x0261, 0x026c, 0x026a, 0x029e, 0x0287, 0x029d, + 0xab53, 0xa7b5, 0xa7bf, 0xa7c3, 0xa794, 0x0282, 0x1d8e, 0xa7c8, 0xa7ca, 0xa7f6, 0x13a0, 0x13ef, 0xff41, + 0xff5a, 0x10428, 0x1044f, 0x104d8, 0x104fb, 0x10cc0, 0x10cf2, 0x118c0, 0x118df, 0x16e60, 0x16e7f, 0x1e922, + 0x1e943 }; - - /* This maps single codepoint to two codepoints. */ - static const struct { - int src_codepoint; - int dest_codepoint0; - int dest_codepoint1; - } double_map[] = { - { 0x00df, 0x0073, 0x0073 }, { 0x0130, 0x0069, 0x0307 }, { 0x0149, 0x02bc, 0x006e }, { 0x01f0, 0x006a, 0x030c }, { 0x0587, 0x0565, 0x0582 }, { 0x1e96, 0x0068, 0x0331 }, - { 0x1e97, 0x0074, 0x0308 }, { 0x1e98, 0x0077, 0x030a }, { 0x1e99, 0x0079, 0x030a }, { 0x1e9a, 0x0061, 0x02be }, { 0x1f50, 0x03c5, 0x0313 }, { 0x1f80, 0x1f00, 0x03b9 }, - { 0x1f81, 0x1f01, 0x03b9 }, { 0x1f82, 0x1f02, 0x03b9 }, { 0x1f83, 0x1f03, 0x03b9 }, { 0x1f84, 0x1f04, 0x03b9 }, { 0x1f85, 0x1f05, 0x03b9 }, { 0x1f86, 0x1f06, 0x03b9 }, - { 0x1f87, 0x1f07, 0x03b9 }, { 0x1f88, 0x1f00, 0x03b9 }, { 0x1f89, 0x1f01, 0x03b9 }, { 0x1f8a, 0x1f02, 0x03b9 }, { 0x1f8b, 0x1f03, 0x03b9 }, { 0x1f8c, 0x1f04, 0x03b9 }, - { 0x1f8d, 0x1f05, 0x03b9 }, { 0x1f8e, 0x1f06, 0x03b9 }, { 0x1f8f, 0x1f07, 0x03b9 }, { 0x1f90, 0x1f20, 0x03b9 }, { 0x1f91, 0x1f21, 0x03b9 }, { 0x1f92, 0x1f22, 0x03b9 }, - { 0x1f93, 0x1f23, 0x03b9 }, { 0x1f94, 0x1f24, 0x03b9 }, { 0x1f95, 0x1f25, 0x03b9 }, { 0x1f96, 0x1f26, 0x03b9 }, { 0x1f97, 0x1f27, 0x03b9 }, { 0x1f98, 0x1f20, 0x03b9 }, - { 0x1f99, 0x1f21, 0x03b9 }, { 0x1f9a, 0x1f22, 0x03b9 }, { 0x1f9b, 0x1f23, 0x03b9 }, { 0x1f9c, 0x1f24, 0x03b9 }, { 0x1f9d, 0x1f25, 0x03b9 }, { 0x1f9e, 0x1f26, 0x03b9 }, - { 0x1f9f, 0x1f27, 0x03b9 }, { 0x1fa0, 0x1f60, 0x03b9 }, { 0x1fa1, 0x1f61, 0x03b9 }, { 0x1fa2, 0x1f62, 0x03b9 }, { 0x1fa3, 0x1f63, 0x03b9 }, { 0x1fa4, 0x1f64, 0x03b9 }, - { 0x1fa5, 0x1f65, 0x03b9 }, { 0x1fa6, 0x1f66, 0x03b9 }, { 0x1fa7, 0x1f67, 0x03b9 }, { 0x1fa8, 0x1f60, 0x03b9 }, { 0x1fa9, 0x1f61, 0x03b9 }, { 0x1faa, 0x1f62, 0x03b9 }, - { 0x1fab, 0x1f63, 0x03b9 }, { 0x1fac, 0x1f64, 0x03b9 }, { 0x1fad, 0x1f65, 0x03b9 }, { 0x1fae, 0x1f66, 0x03b9 }, { 0x1faf, 0x1f67, 0x03b9 }, { 0x1fb2, 0x1f70, 0x03b9 }, - { 0x1fb3, 0x03b1, 0x03b9 }, { 0x1fb4, 0x03ac, 0x03b9 }, { 0x1fb6, 0x03b1, 0x0342 }, { 0x1fbc, 0x03b1, 0x03b9 }, { 0x1fc2, 0x1f74, 0x03b9 }, { 0x1fc3, 0x03b7, 0x03b9 }, - { 0x1fc4, 0x03ae, 0x03b9 }, { 0x1fc6, 0x03b7, 0x0342 }, { 0x1fcc, 0x03b7, 0x03b9 }, { 0x1fd6, 0x03b9, 0x0342 }, { 0x1fe4, 0x03c1, 0x0313 }, { 0x1fe6, 0x03c5, 0x0342 }, - { 0x1ff2, 0x1f7c, 0x03b9 }, { 0x1ff3, 0x03c9, 0x03b9 }, { 0x1ff4, 0x03ce, 0x03b9 }, { 0x1ff6, 0x03c9, 0x0342 }, { 0x1ffc, 0x03c9, 0x03b9 }, { 0xfb00, 0x0066, 0x0066 }, - { 0xfb01, 0x0066, 0x0069 }, { 0xfb02, 0x0066, 0x006c }, { 0xfb05, 0x0073, 0x0074 }, { 0xfb06, 0x0073, 0x0074 }, { 0xfb13, 0x0574, 0x0576 }, { 0xfb14, 0x0574, 0x0565 }, - { 0xfb15, 0x0574, 0x056b }, { 0xfb16, 0x057e, 0x0576 }, { 0xfb17, 0x0574, 0x056d } + static const unsigned FOLD_MAP_2[] = { + S(0x00df), S(0x0130), S(0x0149), S(0x01f0), S(0x0587), S(0x1e96), S(0x1e97), S(0x1e98), S(0x1e99), + S(0x1e9a), S(0x1e9e), S(0x1f50), R(0x1f80,0x1f87), R(0x1f88,0x1f8f), R(0x1f90,0x1f97), R(0x1f98,0x1f9f), + R(0x1fa0,0x1fa7), R(0x1fa8,0x1faf), S(0x1fb2), S(0x1fb3), S(0x1fb4), S(0x1fb6), S(0x1fbc), S(0x1fc2), + S(0x1fc3), S(0x1fc4), S(0x1fc6), S(0x1fcc), S(0x1fd6), S(0x1fe4), S(0x1fe6), S(0x1ff2), S(0x1ff3), + S(0x1ff4), S(0x1ff6), S(0x1ffc), S(0xfb00), S(0xfb01), S(0xfb02), S(0xfb05), S(0xfb06), S(0xfb13), + S(0xfb14), S(0xfb15), S(0xfb16), S(0xfb17) }; - - /* This maps single codepoint to three codepoints. */ + static const unsigned FOLD_MAP_2_DATA[] = { + 0x0073,0x0073, 0x0069,0x0307, 0x02bc,0x006e, 0x006a,0x030c, 0x0565,0x0582, 0x0068,0x0331, 0x0074,0x0308, + 0x0077,0x030a, 0x0079,0x030a, 0x0061,0x02be, 0x0073,0x0073, 0x03c5,0x0313, 0x1f00,0x03b9, 0x1f07,0x03b9, + 0x1f00,0x03b9, 0x1f07,0x03b9, 0x1f20,0x03b9, 0x1f27,0x03b9, 0x1f20,0x03b9, 0x1f27,0x03b9, 0x1f60,0x03b9, + 0x1f67,0x03b9, 0x1f60,0x03b9, 0x1f67,0x03b9, 0x1f70,0x03b9, 0x03b1,0x03b9, 0x03ac,0x03b9, 0x03b1,0x0342, + 0x03b1,0x03b9, 0x1f74,0x03b9, 0x03b7,0x03b9, 0x03ae,0x03b9, 0x03b7,0x0342, 0x03b7,0x03b9, 0x03b9,0x0342, + 0x03c1,0x0313, 0x03c5,0x0342, 0x1f7c,0x03b9, 0x03c9,0x03b9, 0x03ce,0x03b9, 0x03c9,0x0342, 0x03c9,0x03b9, + 0x0066,0x0066, 0x0066,0x0069, 0x0066,0x006c, 0x0073,0x0074, 0x0073,0x0074, 0x0574,0x0576, 0x0574,0x0565, + 0x0574,0x056b, 0x057e,0x0576, 0x0574,0x056d + }; + static const unsigned FOLD_MAP_3[] = { + S(0x0390), S(0x03b0), S(0x1f52), S(0x1f54), S(0x1f56), S(0x1fb7), S(0x1fc7), S(0x1fd2), S(0x1fd3), + S(0x1fd7), S(0x1fe2), S(0x1fe3), S(0x1fe7), S(0x1ff7), S(0xfb03), S(0xfb04) + }; + static const unsigned FOLD_MAP_3_DATA[] = { + 0x03b9,0x0308,0x0301, 0x03c5,0x0308,0x0301, 0x03c5,0x0313,0x0300, 0x03c5,0x0313,0x0301, + 0x03c5,0x0313,0x0342, 0x03b1,0x0342,0x03b9, 0x03b7,0x0342,0x03b9, 0x03b9,0x0308,0x0300, + 0x03b9,0x0308,0x0301, 0x03b9,0x0308,0x0342, 0x03c5,0x0308,0x0300, 0x03c5,0x0308,0x0301, + 0x03c5,0x0308,0x0342, 0x03c9,0x0342,0x03b9, 0x0066,0x0066,0x0069, 0x0066,0x0066,0x006c + }; +#undef R +#undef S static const struct { - int src_codepoint; - int dest_codepoint0; - int dest_codepoint1; - int dest_codepoint2; - } triple_map[] = { - { 0x0390, 0x03b9, 0x0308, 0x0301 }, { 0x03b0, 0x03c5, 0x0308, 0x0301 }, { 0x1f52, 0x03c5, 0x0313, 0x0300 }, { 0x1f54, 0x03c5, 0x0313, 0x0301 }, - { 0x1f56, 0x03c5, 0x0313, 0x0342 }, { 0x1fb7, 0x03b1, 0x0342, 0x03b9 }, { 0x1fc7, 0x03b7, 0x0342, 0x03b9 }, { 0x1fd2, 0x03b9, 0x0308, 0x0300 }, - { 0x1fd3, 0x03b9, 0x0308, 0x0301 }, { 0x1fd7, 0x03b9, 0x0308, 0x0342 }, { 0x1fe2, 0x03c5, 0x0308, 0x0300 }, { 0x1fe3, 0x03c5, 0x0308, 0x0301 }, - { 0x1fe7, 0x03c5, 0x0308, 0x0342 }, { 0x1ff7, 0x03c9, 0x0342, 0x03b9 }, { 0xfb03, 0x0066, 0x0066, 0x0069 }, { 0xfb04, 0x0066, 0x0066, 0x006c } + const unsigned* map; + const unsigned* data; + size_t map_size; + unsigned n_codepoints; + } FOLD_MAP_LIST[] = { + { FOLD_MAP_1, FOLD_MAP_1_DATA, SIZEOF_ARRAY(FOLD_MAP_1), 1 }, + { FOLD_MAP_2, FOLD_MAP_2_DATA, SIZEOF_ARRAY(FOLD_MAP_2), 2 }, + { FOLD_MAP_3, FOLD_MAP_3_DATA, SIZEOF_ARRAY(FOLD_MAP_3), 3 } }; int i; @@ -658,41 +724,37 @@ struct MD_UNICODE_FOLD_INFO_tag { return; } - for(i = 0; i < SIZEOF_ARRAY(range_map); i++) { - if(range_map[i].min_codepoint <= codepoint && codepoint <= range_map[i].max_codepoint) { - info->codepoints[0] = codepoint + range_map[i].offset; - info->n_codepoints = 1; - return; - } - } - - for(i = 0; i < SIZEOF_ARRAY(single_map); i++) { - if(codepoint == single_map[i].src_codepoint) { - info->codepoints[0] = single_map[i].dest_codepoint; - info->n_codepoints = 1; - return; - } - } - - for(i = 0; i < SIZEOF_ARRAY(double_map); i++) { - if(codepoint == double_map[i].src_codepoint) { - info->codepoints[0] = double_map[i].dest_codepoint0; - info->codepoints[1] = double_map[i].dest_codepoint1; - info->n_codepoints = 2; - return; - } - } + /* Try to locate the codepoint in any of the maps. */ + for(i = 0; i < (int) SIZEOF_ARRAY(FOLD_MAP_LIST); i++) { + int index; + + index = md_unicode_bsearch__(codepoint, FOLD_MAP_LIST[i].map, FOLD_MAP_LIST[i].map_size); + if(index >= 0) { + /* Found the mapping. */ + unsigned n_codepoints = FOLD_MAP_LIST[i].n_codepoints; + const unsigned* map = FOLD_MAP_LIST[i].map; + const unsigned* codepoints = FOLD_MAP_LIST[i].data + (index * n_codepoints); + + memcpy(info->codepoints, codepoints, sizeof(unsigned) * n_codepoints); + info->n_codepoints = n_codepoints; + + if(FOLD_MAP_LIST[i].map[index] != codepoint) { + /* The found mapping maps whole range of codepoints, + * i.e. we have to offset info->codepoints[0] accordingly. */ + if((map[index] & 0x00ffffff)+1 == codepoints[0]) { + /* Alternating type of the range. */ + info->codepoints[0] = codepoint + ((codepoint & 0x1) == (map[index] & 0x1) ? 1 : 0); + } else { + /* Range to range kind of mapping. */ + info->codepoints[0] += (codepoint - (map[index] & 0x00ffffff)); + } + } - for(i = 0; i < SIZEOF_ARRAY(triple_map); i++) { - if(codepoint == triple_map[i].src_codepoint) { - info->codepoints[0] = triple_map[i].dest_codepoint0; - info->codepoints[1] = triple_map[i].dest_codepoint1; - info->codepoints[2] = triple_map[i].dest_codepoint2; - info->n_codepoints = 3; return; } } + /* No mapping found. Map the codepoint to itself. */ info->codepoints[0] = codepoint; info->n_codepoints = 1; } @@ -704,7 +766,7 @@ struct MD_UNICODE_FOLD_INFO_tag { #define IS_UTF16_SURROGATE_LO(word) (((WORD)(word) & 0xfc00) == 0xdc00) #define UTF16_DECODE_SURROGATE(hi, lo) (0x10000 + ((((unsigned)(hi) & 0x3ff) << 10) | (((unsigned)(lo) & 0x3ff) << 0))) - static int + static unsigned md_decode_utf16le__(const CHAR* str, SZ str_size, SZ* p_size) { if(IS_UTF16_SURROGATE_HI(str[0])) { @@ -720,7 +782,7 @@ struct MD_UNICODE_FOLD_INFO_tag { return str[0]; } - static int + static unsigned md_decode_utf16le_before__(MD_CTX* ctx, OFF off) { if(off > 2 && IS_UTF16_SURROGATE_HI(CH(off-2)) && IS_UTF16_SURROGATE_LO(CH(off-1))) @@ -749,7 +811,7 @@ struct MD_UNICODE_FOLD_INFO_tag { #define IS_UTF8_LEAD4(byte) (((unsigned char)(byte) & 0xf8) == 0xf0) #define IS_UTF8_TAIL(byte) (((unsigned char)(byte) & 0xc0) == 0x80) - static int + static unsigned md_decode_utf8__(const CHAR* str, SZ str_size, SZ* p_size) { if(!IS_UTF8_LEAD1(str[0])) { @@ -785,10 +847,10 @@ struct MD_UNICODE_FOLD_INFO_tag { if(p_size != NULL) *p_size = 1; - return str[0]; + return (unsigned) str[0]; } - static int + static unsigned md_decode_utf8_before__(MD_CTX* ctx, OFF off) { if(!IS_UTF8_LEAD1(CH(off-1))) { @@ -808,7 +870,7 @@ struct MD_UNICODE_FOLD_INFO_tag { (((unsigned int)CH(off-1) & 0x3f) << 0); } - return CH(off-1); + return (unsigned) CH(off-1); } #define ISUNICODEWHITESPACE_(codepoint) md_is_unicode_whitespace__(codepoint) @@ -818,7 +880,7 @@ struct MD_UNICODE_FOLD_INFO_tag { #define ISUNICODEPUNCT(off) md_is_unicode_punct__(md_decode_utf8__(STR(off), ctx->size - (off), NULL)) #define ISUNICODEPUNCTBEFORE(off) md_is_unicode_punct__(md_decode_utf8_before__(ctx, off)) - static inline int + static inline unsigned md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_char_size) { return md_decode_utf8__(str+off, str_size-off, p_char_size); @@ -832,7 +894,7 @@ struct MD_UNICODE_FOLD_INFO_tag { #define ISUNICODEPUNCTBEFORE(off) ISPUNCT((off)-1) static inline void - md_get_unicode_fold_info(int codepoint, MD_UNICODE_FOLD_INFO* info) + md_get_unicode_fold_info(unsigned codepoint, MD_UNICODE_FOLD_INFO* info) { info->codepoints[0] = codepoint; if(ISUPPER_(codepoint)) @@ -840,11 +902,11 @@ struct MD_UNICODE_FOLD_INFO_tag { info->n_codepoints = 1; } - static inline int + static inline unsigned md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_size) { *p_size = 1; - return str[off]; + return (unsigned) str[off]; } #endif @@ -868,6 +930,8 @@ md_merge_lines(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, int n_lines, int line_index = 0; OFF off = beg; + MD_UNUSED(n_lines); + while(1) { const MD_LINE* line = &lines[line_index]; OFF line_end = line->end; @@ -881,7 +945,7 @@ md_merge_lines(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, int n_lines, } if(off >= end) { - *p_size = ptr - buffer; + *p_size = (MD_SIZE)(ptr - buffer); return; } @@ -918,7 +982,7 @@ static OFF md_skip_unicode_whitespace(const CHAR* label, OFF off, SZ size) { SZ char_size; - int codepoint; + unsigned codepoint; while(off < size) { codepoint = md_decode_unicode(label, off, size, &char_size); @@ -1061,108 +1125,93 @@ done: } static int -md_is_html_comment(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end) +md_scan_for_html_closer(MD_CTX* ctx, const MD_CHAR* str, MD_SIZE len, + const MD_LINE* lines, int n_lines, + OFF beg, OFF max_end, OFF* p_end, + OFF* p_scan_horizon) { OFF off = beg; int i = 0; - MD_ASSERT(CH(beg) == _T('<')); - - if(off + 4 >= lines[0].end) - return FALSE; - if(CH(off+1) != _T('!') || CH(off+2) != _T('-') || CH(off+3) != _T('-')) - return FALSE; - off += 4; - - /* ">" and "->" must follow the opening. */ - if(off < lines[0].end && CH(off) == _T('>')) - return FALSE; - if(off+1 < lines[0].end && CH(off) == _T('-') && CH(off+1) == _T('>')) + if(off < *p_scan_horizon && *p_scan_horizon >= max_end - len) { + /* We have already scanned the range up to the max_end so we know + * there is nothing to see. */ return FALSE; + } - while(1) { - while(off + 2 < lines[i].end) { - if(CH(off) == _T('-') && CH(off+1) == _T('-')) { - if(CH(off+2) == _T('>')) { - /* Success. */ - off += 2; - goto done; - } else { - /* "--" is prohibited inside the comment. */ - return FALSE; - } + while(TRUE) { + while(off + len <= lines[i].end && off + len <= max_end) { + if(md_ascii_eq(STR(off), str, len)) { + /* Success. */ + *p_end = off + len; + return TRUE; } - off++; } i++; - if(i >= n_lines) + if(off >= max_end || i >= n_lines) { + /* Failure. */ + *p_scan_horizon = off; return FALSE; + } off = lines[i].beg; - - if(off >= max_end) - return FALSE; } - -done: - if(off >= max_end) - return FALSE; - - *p_end = off+1; - return TRUE; } static int -md_is_html_processing_instruction(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end) +md_is_html_comment(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end) { OFF off = beg; - int i = 0; MD_ASSERT(CH(beg) == _T('<')); - if(off + 2 >= lines[0].end) + if(off + 4 >= lines[0].end) return FALSE; - if(CH(off+1) != _T('?')) + if(CH(off+1) != _T('!') || CH(off+2) != _T('-') || CH(off+3) != _T('-')) return FALSE; - off += 2; + off += 4; - while(1) { - while(off + 1 < lines[i].end) { - if(CH(off) == _T('?') && CH(off+1) == _T('>')) { - /* Success. */ - off++; - goto done; - } + /* ">" and "->" must not follow the opening. */ + if(off < lines[0].end && CH(off) == _T('>')) + return FALSE; + if(off+1 < lines[0].end && CH(off) == _T('-') && CH(off+1) == _T('>')) + return FALSE; - off++; + /* HTML comment must not contain "--", so we scan just for "--" instead + * of "-->" and verify manually that '>' follows. */ + if(md_scan_for_html_closer(ctx, _T("--"), 2, + lines, n_lines, off, max_end, p_end, &ctx->html_comment_horizon)) + { + if(*p_end < max_end && CH(*p_end) == _T('>')) { + *p_end = *p_end + 1; + return TRUE; } + } - i++; - if(i >= n_lines) - return FALSE; + return FALSE; +} - off = lines[i].beg; - if(off >= max_end) - return FALSE; - } +static int +md_is_html_processing_instruction(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end) +{ + OFF off = beg; -done: - if(off >= max_end) + if(off + 2 >= lines[0].end) return FALSE; + if(CH(off+1) != _T('?')) + return FALSE; + off += 2; - *p_end = off+1; - return TRUE; + return md_scan_for_html_closer(ctx, _T("?>"), 2, + lines, n_lines, off, max_end, p_end, &ctx->html_proc_instr_horizon); } static int md_is_html_declaration(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end) { OFF off = beg; - int i = 0; - - MD_ASSERT(CH(beg) == _T('<')); if(off + 2 >= lines[0].end) return FALSE; @@ -1179,41 +1228,17 @@ md_is_html_declaration(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, if(off < lines[0].end && !ISWHITESPACE(off)) return FALSE; - while(1) { - while(off < lines[i].end) { - if(CH(off) == _T('>')) { - /* Success. */ - goto done; - } + return md_scan_for_html_closer(ctx, _T(">"), 1, + lines, n_lines, off, max_end, p_end, &ctx->html_decl_horizon); +} - off++; - } +static int +md_is_html_cdata(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end) +{ + static const CHAR open_str[] = _T("<![CDATA["); + static const SZ open_size = SIZEOF_ARRAY(open_str) - 1; - i++; - if(i >= n_lines) - return FALSE; - - off = lines[i].beg; - if(off >= max_end) - return FALSE; - } - -done: - if(off >= max_end) - return FALSE; - - *p_end = off+1; - return TRUE; -} - -static int -md_is_html_cdata(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end) -{ - static const CHAR open_str[] = _T("<![CDATA["); - static const SZ open_size = SIZEOF_ARRAY(open_str) - 1; - - OFF off = beg; - int i = 0; + OFF off = beg; if(off + open_size >= lines[0].end) return FALSE; @@ -1221,49 +1246,22 @@ md_is_html_cdata(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF ma return FALSE; off += open_size; - while(1) { - while(off + 2 < lines[i].end) { - if(CH(off) == _T(']') && CH(off+1) == _T(']') && CH(off+2) == _T('>')) { - /* Success. */ - off += 2; - goto done; - } - - off++; - } - - i++; - if(i >= n_lines) - return FALSE; - - off = lines[i].beg; - if(off >= max_end) - return FALSE; - } - -done: - if(off >= max_end) - return FALSE; + if(lines[n_lines-1].end < max_end) + max_end = lines[n_lines-1].end - 2; - *p_end = off+1; - return TRUE; + return md_scan_for_html_closer(ctx, _T("]]>"), 3, + lines, n_lines, off, max_end, p_end, &ctx->html_cdata_horizon); } static int md_is_html_any(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end) { - if(md_is_html_tag(ctx, lines, n_lines, beg, max_end, p_end) == TRUE) - return TRUE; - if(md_is_html_comment(ctx, lines, n_lines, beg, max_end, p_end) == TRUE) - return TRUE; - if(md_is_html_processing_instruction(ctx, lines, n_lines, beg, max_end, p_end) == TRUE) - return TRUE; - if(md_is_html_declaration(ctx, lines, n_lines, beg, max_end, p_end) == TRUE) - return TRUE; - if(md_is_html_cdata(ctx, lines, n_lines, beg, max_end, p_end) == TRUE) - return TRUE; - - return FALSE; + MD_ASSERT(CH(beg) == _T('<')); + return (md_is_html_tag(ctx, lines, n_lines, beg, max_end, p_end) || + md_is_html_comment(ctx, lines, n_lines, beg, max_end, p_end) || + md_is_html_processing_instruction(ctx, lines, n_lines, beg, max_end, p_end) || + md_is_html_declaration(ctx, lines, n_lines, beg, max_end, p_end) || + md_is_html_cdata(ctx, lines, n_lines, beg, max_end, p_end)); } @@ -1275,11 +1273,12 @@ static int md_is_hex_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end) { OFF off = beg; + MD_UNUSED(ctx); while(off < max_end && ISXDIGIT_(text[off]) && off - beg <= 8) off++; - if(1 <= off - beg && off - beg <= 8) { + if(1 <= off - beg && off - beg <= 6) { *p_end = off; return TRUE; } else { @@ -1291,11 +1290,12 @@ static int md_is_dec_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end) { OFF off = beg; + MD_UNUSED(ctx); while(off < max_end && ISDIGIT_(text[off]) && off - beg <= 8) off++; - if(1 <= off - beg && off - beg <= 8) { + if(1 <= off - beg && off - beg <= 7) { *p_end = off; return TRUE; } else { @@ -1307,8 +1307,9 @@ static int md_is_named_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end) { OFF off = beg; + MD_UNUSED(ctx); - if(off <= max_end && ISALPHA_(text[off])) + if(off < max_end && ISALPHA_(text[off])) off++; else return FALSE; @@ -1333,9 +1334,9 @@ md_is_entity_str(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end MD_ASSERT(text[off] == _T('&')); off++; - if(off+1 < max_end && text[off] == _T('#') && (text[off+1] == _T('x') || text[off+1] == _T('X'))) + if(off+2 < max_end && text[off] == _T('#') && (text[off+1] == _T('x') || text[off+1] == _T('X'))) is_contents = md_is_hex_entity_contents(ctx, text, off+2, max_end, &off); - else if(off < max_end && CH(off) == _T('#')) + else if(off+1 < max_end && text[off] == _T('#')) is_contents = md_is_dec_entity_contents(ctx, text, off+1, max_end, &off); else is_contents = md_is_named_entity_contents(ctx, text, off, max_end, &off); @@ -1381,16 +1382,18 @@ md_build_attr_append_substr(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build, MD_TEXTTYPE* new_substr_types; OFF* new_substr_offsets; - build->substr_alloc = (build->substr_alloc == 0 ? 8 : build->substr_alloc * 2); - + build->substr_alloc = (build->substr_alloc > 0 + ? build->substr_alloc + build->substr_alloc / 2 + : 8); new_substr_types = (MD_TEXTTYPE*) realloc(build->substr_types, - (build->substr_alloc+1) * sizeof(MD_TEXTTYPE)); + build->substr_alloc * sizeof(MD_TEXTTYPE)); if(new_substr_types == NULL) { MD_LOG("realloc() failed."); return -1; } + /* Note +1 to reserve space for final offset (== raw_size). */ new_substr_offsets = (OFF*) realloc(build->substr_offsets, - build->substr_alloc * sizeof(OFF)); + (build->substr_alloc+1) * sizeof(OFF)); if(new_substr_offsets == NULL) { MD_LOG("realloc() failed."); free(new_substr_types); @@ -1410,6 +1413,8 @@ md_build_attr_append_substr(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build, static void md_free_attribute(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build) { + MD_UNUSED(ctx); + if(build->substr_alloc > 0) { free(build->text); free(build->substr_types); @@ -1507,8 +1512,8 @@ abort: *** Dictionary of Reference Definitions *** *********************************************/ -#define MD_FNV1A_BASE 2166136261 -#define MD_FNV1A_PRIME 16777619 +#define MD_FNV1A_BASE 2166136261U +#define MD_FNV1A_PRIME 16777619U static inline unsigned md_fnv1a(unsigned base, const void* data, size_t n) @@ -1530,12 +1535,12 @@ struct MD_REF_DEF_tag { CHAR* label; CHAR* title; unsigned hash; - SZ label_size : 24; - unsigned label_needs_free : 1; - unsigned title_needs_free : 1; + SZ label_size; SZ title_size; OFF dest_beg; OFF dest_end; + unsigned char label_needs_free : 1; + unsigned char title_needs_free : 1; }; /* Label equivalence is quite complicated with regards to whitespace and case @@ -1547,7 +1552,7 @@ md_link_label_hash(const CHAR* label, SZ size) { unsigned hash = MD_FNV1A_BASE; OFF off; - int codepoint; + unsigned codepoint; int is_whitespace = FALSE; off = md_skip_unicode_whitespace(label, 0, size); @@ -1559,25 +1564,47 @@ md_link_label_hash(const CHAR* label, SZ size) if(is_whitespace) { codepoint = ' '; - hash = md_fnv1a(hash, &codepoint, 1 * sizeof(int)); - + hash = md_fnv1a(hash, &codepoint, sizeof(unsigned)); off = md_skip_unicode_whitespace(label, off, size); } else { MD_UNICODE_FOLD_INFO fold_info; md_get_unicode_fold_info(codepoint, &fold_info); - hash = md_fnv1a(hash, fold_info.codepoints, fold_info.n_codepoints * sizeof(int)); - + hash = md_fnv1a(hash, fold_info.codepoints, fold_info.n_codepoints * sizeof(unsigned)); off += char_size; } } - if(!is_whitespace) { - codepoint = ' '; - hash = md_fnv1a(hash, &codepoint, 1 * sizeof(int)); + return hash; +} + +static OFF +md_link_label_cmp_load_fold_info(const CHAR* label, OFF off, SZ size, + MD_UNICODE_FOLD_INFO* fold_info) +{ + unsigned codepoint; + SZ char_size; + + if(off >= size) { + /* Treat end of a link label as a whitespace. */ + goto whitespace; + } + + codepoint = md_decode_unicode(label, off, size, &char_size); + off += char_size; + if(ISUNICODEWHITESPACE_(codepoint)) { + /* Treat all whitespace as equivalent */ + goto whitespace; } - return hash; + /* Get real folding info. */ + md_get_unicode_fold_info(codepoint, fold_info); + return off; + +whitespace: + fold_info->codepoints[0] = _T(' '); + fold_info->n_codepoints = 1; + return md_skip_unicode_whitespace(label, off, size); } static int @@ -1585,55 +1612,33 @@ md_link_label_cmp(const CHAR* a_label, SZ a_size, const CHAR* b_label, SZ b_size { OFF a_off; OFF b_off; + MD_UNICODE_FOLD_INFO a_fi = { { 0 }, 0 }; + MD_UNICODE_FOLD_INFO b_fi = { { 0 }, 0 }; + OFF a_fi_off = 0; + OFF b_fi_off = 0; + int cmp; - /* The slow path, with Unicode case folding and Unicode whitespace collapsing. */ a_off = md_skip_unicode_whitespace(a_label, 0, a_size); b_off = md_skip_unicode_whitespace(b_label, 0, b_size); - while(a_off < a_size || b_off < b_size) { - int a_codepoint, b_codepoint; - SZ a_char_size, b_char_size; - int a_is_whitespace, b_is_whitespace; - - if(a_off < a_size) { - a_codepoint = md_decode_unicode(a_label, a_off, a_size, &a_char_size); - a_is_whitespace = ISUNICODEWHITESPACE_(a_codepoint) || ISNEWLINE_(a_label[a_off]); - } else { - /* Treat end of label as a whitespace. */ - a_codepoint = -1; - a_is_whitespace = TRUE; + while(a_off < a_size || a_fi_off < a_fi.n_codepoints || + b_off < b_size || b_fi_off < b_fi.n_codepoints) + { + /* If needed, load fold info for next char. */ + if(a_fi_off >= a_fi.n_codepoints) { + a_fi_off = 0; + a_off = md_link_label_cmp_load_fold_info(a_label, a_off, a_size, &a_fi); } - - if(b_off < b_size) { - b_codepoint = md_decode_unicode(b_label, b_off, b_size, &b_char_size); - b_is_whitespace = ISUNICODEWHITESPACE_(b_codepoint) || ISNEWLINE_(b_label[b_off]); - } else { - /* Treat end of label as a whitespace. */ - b_codepoint = -1; - b_is_whitespace = TRUE; + if(b_fi_off >= b_fi.n_codepoints) { + b_fi_off = 0; + b_off = md_link_label_cmp_load_fold_info(b_label, b_off, b_size, &b_fi); } - if(a_is_whitespace || b_is_whitespace) { - if(!a_is_whitespace || !b_is_whitespace) - return (a_is_whitespace ? -1 : +1); - - a_off = md_skip_unicode_whitespace(a_label, a_off, a_size); - b_off = md_skip_unicode_whitespace(b_label, b_off, b_size); - } else { - MD_UNICODE_FOLD_INFO a_fold_info, b_fold_info; - int cmp; - - md_get_unicode_fold_info(a_codepoint, &a_fold_info); - md_get_unicode_fold_info(b_codepoint, &b_fold_info); + cmp = b_fi.codepoints[b_fi_off] - a_fi.codepoints[a_fi_off]; + if(cmp != 0) + return cmp; - if(a_fold_info.n_codepoints != b_fold_info.n_codepoints) - return (a_fold_info.n_codepoints - b_fold_info.n_codepoints); - cmp = memcmp(a_fold_info.codepoints, b_fold_info.codepoints, a_fold_info.n_codepoints * sizeof(int)); - if(cmp != 0) - return cmp; - - a_off += a_char_size; - b_off += b_char_size; - } + a_fi_off++; + b_fi_off++; } return 0; @@ -1661,7 +1666,7 @@ md_ref_def_cmp(const void* a, const void* b) } static int -md_ref_def_cmp_stable(const void* a, const void* b) +md_ref_def_cmp_for_sort(const void* a, const void* b) { int cmp; @@ -1714,6 +1719,7 @@ md_build_ref_def_hashtable(MD_CTX* ctx) bucket = ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size]; if(bucket == NULL) { + /* The bucket is empty. Make it just point to the def. */ ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size] = def; continue; } @@ -1725,12 +1731,12 @@ md_build_ref_def_hashtable(MD_CTX* ctx) MD_REF_DEF* old_def = (MD_REF_DEF*) bucket; if(md_link_label_cmp(def->label, def->label_size, old_def->label, old_def->label_size) == 0) { - /* Ignore this ref. def. */ + /* Duplicate label: Ignore this ref. def. */ continue; } - /* Make the bucket capable of holding more ref. defs. */ - list = (MD_REF_DEF_LIST*) malloc(sizeof(MD_REF_DEF_LIST) + 4 * sizeof(MD_REF_DEF)); + /* Make the bucket complex, i.e. able to hold more ref. defs. */ + list = (MD_REF_DEF_LIST*) malloc(sizeof(MD_REF_DEF_LIST) + 2 * sizeof(MD_REF_DEF*)); if(list == NULL) { MD_LOG("malloc() failed."); goto abort; @@ -1738,22 +1744,28 @@ md_build_ref_def_hashtable(MD_CTX* ctx) list->ref_defs[0] = old_def; list->ref_defs[1] = def; list->n_ref_defs = 2; - list->alloc_ref_defs = 4; + list->alloc_ref_defs = 2; ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size] = list; continue; } - /* Append the def to the bucket list. */ + /* Append the def to the complex bucket list. + * + * Note in this case we ignore potential duplicates to avoid expensive + * iterating over the complex bucket. Below, we revisit all the complex + * buckets and handle it more cheaply after the complex bucket contents + * is sorted. */ list = (MD_REF_DEF_LIST*) bucket; if(list->n_ref_defs >= list->alloc_ref_defs) { + int alloc_ref_defs = list->alloc_ref_defs + list->alloc_ref_defs / 2; MD_REF_DEF_LIST* list_tmp = (MD_REF_DEF_LIST*) realloc(list, - sizeof(MD_REF_DEF_LIST) + 2 * list->alloc_ref_defs * sizeof(MD_REF_DEF)); + sizeof(MD_REF_DEF_LIST) + alloc_ref_defs * sizeof(MD_REF_DEF*)); if(list_tmp == NULL) { MD_LOG("realloc() failed."); goto abort; } list = list_tmp; - list->alloc_ref_defs *= 2; + list->alloc_ref_defs = alloc_ref_defs; ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size] = list; } @@ -1772,9 +1784,12 @@ md_build_ref_def_hashtable(MD_CTX* ctx) continue; list = (MD_REF_DEF_LIST*) bucket; - qsort(list->ref_defs, list->n_ref_defs, sizeof(MD_REF_DEF*), md_ref_def_cmp_stable); + qsort(list->ref_defs, list->n_ref_defs, sizeof(MD_REF_DEF*), md_ref_def_cmp_for_sort); - /* Disable duplicates. */ + /* Disable all duplicates in the complex bucket by forcing all such + * records to point to the 1st such ref. def. I.e. no matter which + * record is found during the lookup, it will always point to the right + * ref. def. in ctx->ref_defs[]. */ for(j = 1; j < list->n_ref_defs; j++) { if(md_ref_def_cmp(&list->ref_defs[j-1], &list->ref_defs[j]) == 0) list->ref_defs[j] = list->ref_defs[j-1]; @@ -1881,13 +1896,15 @@ md_is_link_label(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, return FALSE; off++; - while(line_index < n_lines) { + while(1) { OFF line_end = lines[line_index].end; while(off < line_end) { if(CH(off) == _T('\\') && off+1 < ctx->size && (ISPUNCT(off+1) || ISNEWLINE(off+1))) { - if(contents_end == 0) + if(contents_end == 0) { contents_beg = off; + *p_beg_line_index = line_index; + } contents_end = off + 2; off += 2; } else if(CH(off) == _T('[')) { @@ -1905,7 +1922,7 @@ md_is_link_label(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, return FALSE; } } else { - int codepoint; + unsigned codepoint; SZ char_size; codepoint = md_decode_unicode(ctx->text, off, ctx->size, &char_size); @@ -1927,7 +1944,10 @@ md_is_link_label(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, line_index++; len++; - off = lines[line_index].beg; + if(line_index < n_lines) + off = lines[line_index].beg; + else + break; } return FALSE; @@ -1949,7 +1969,7 @@ md_is_link_destination_A(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end, continue; } - if(ISWHITESPACE(off) || CH(off) == _T('<')) + if(ISNEWLINE(off) || CH(off) == _T('<')) return FALSE; if(CH(off) == _T('>')) { @@ -2008,6 +2028,16 @@ md_is_link_destination_B(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end, return TRUE; } +static inline int +md_is_link_destination(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end, + OFF* p_contents_beg, OFF* p_contents_end) +{ + if(CH(beg) == _T('<')) + return md_is_link_destination_A(ctx, beg, max_end, p_end, p_contents_beg, p_contents_end); + else + return md_is_link_destination_B(ctx, beg, max_end, p_end, p_contents_beg, p_contents_end); +} + static int md_is_link_title(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF* p_end, int* p_beg_line_index, int* p_end_line_index, @@ -2017,7 +2047,7 @@ md_is_link_title(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, CHAR closer_char; int line_index = 0; - /* Optional white space with up to one line break. */ + /* White space with up to one line break. */ while(off < lines[line_index].end && ISWHITESPACE(off)) off++; if(off >= lines[line_index].end) { @@ -2026,6 +2056,8 @@ md_is_link_title(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, return FALSE; off = lines[line_index].beg; } + if(off == beg) + return FALSE; *p_beg_line_index = line_index; @@ -2052,6 +2084,9 @@ md_is_link_title(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, *p_end = off+1; *p_end_line_index = line_index; return TRUE; + } else if(closer_char == _T(')') && CH(off) == _T('(')) { + /* ()-style title cannot contain (unescaped '(')) */ + return FALSE; } off++; @@ -2065,35 +2100,30 @@ md_is_link_title(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, /* Returns 0 if it is not a reference definition. * - * Returns N > 0 if it is not a reference definition (then N corresponds - * to the number of lines forming it). In this case the definition is stored - * for resolving any links referring to it. + * Returns N > 0 if it is a reference definition. N then corresponds to the + * number of lines forming it). In this case the definition is stored for + * resolving any links referring to it. * - * If there is an error (cannot alloc memory for storing it), we return -1. + * Returns -1 in case of an error (out of memory). */ static int -md_is_link_reference_definition_helper( - MD_CTX* ctx, const MD_LINE* lines, int n_lines, - int (*is_link_dest_fn)(MD_CTX*, OFF, OFF, OFF*, OFF*, OFF*)) +md_is_link_reference_definition(MD_CTX* ctx, const MD_LINE* lines, int n_lines) { OFF label_contents_beg; OFF label_contents_end; int label_contents_line_index = -1; - int label_is_multiline; - CHAR* label; - SZ label_size; - int label_needs_free = FALSE; + int label_is_multiline = FALSE; OFF dest_contents_beg; OFF dest_contents_end; OFF title_contents_beg; OFF title_contents_end; int title_contents_line_index; - int title_is_multiline; + int title_is_multiline = FALSE; OFF off; int line_index = 0; int tmp_line_index; - MD_REF_DEF* def; - int ret; + MD_REF_DEF* def = NULL; + int ret = 0; /* Link label. */ if(!md_is_link_label(ctx, lines, n_lines, lines[0].beg, @@ -2118,8 +2148,8 @@ md_is_link_reference_definition_helper( } /* Link destination. */ - if(!is_link_dest_fn(ctx, off, lines[line_index].end, - &off, &dest_contents_beg, &dest_contents_end)) + if(!md_is_link_destination(ctx, off, lines[line_index].end, + &off, &dest_contents_beg, &dest_contents_end)) return FALSE; /* (Optional) title. Note we interpret it as an title only if nothing @@ -2144,74 +2174,57 @@ md_is_link_reference_definition_helper( if(off < lines[line_index].end) return FALSE; - /* Construct label. */ - if(!label_is_multiline) { - label = (CHAR*) STR(label_contents_beg); - label_size = label_contents_end - label_contents_beg; - label_needs_free = FALSE; - } else { - MD_CHECK(md_merge_lines_alloc(ctx, label_contents_beg, label_contents_end, - lines + label_contents_line_index, n_lines - label_contents_line_index, - _T(' '), &label, &label_size)); - label_needs_free = TRUE; - } - - /* Store the reference definition. */ + /* So, it _is_ a reference definition. Remember it. */ if(ctx->n_ref_defs >= ctx->alloc_ref_defs) { MD_REF_DEF* new_defs; - ctx->alloc_ref_defs = (ctx->alloc_ref_defs > 0 ? ctx->alloc_ref_defs * 2 : 16); + ctx->alloc_ref_defs = (ctx->alloc_ref_defs > 0 + ? ctx->alloc_ref_defs + ctx->alloc_ref_defs / 2 + : 16); new_defs = (MD_REF_DEF*) realloc(ctx->ref_defs, ctx->alloc_ref_defs * sizeof(MD_REF_DEF)); if(new_defs == NULL) { MD_LOG("realloc() failed."); - ret = -1; goto abort; } ctx->ref_defs = new_defs; } - def = &ctx->ref_defs[ctx->n_ref_defs]; memset(def, 0, sizeof(MD_REF_DEF)); - def->label = label; - def->label_size = label_size; - def->label_needs_free = label_needs_free; - - def->dest_beg = dest_contents_beg; - def->dest_end = dest_contents_end; - - if(title_contents_beg >= title_contents_end) { - def->title = NULL; - def->title_size = 0; - } else if(!title_is_multiline) { - def->title = (CHAR*) STR(title_contents_beg); - def->title_size = title_contents_end - title_contents_beg; + if(label_is_multiline) { + MD_CHECK(md_merge_lines_alloc(ctx, label_contents_beg, label_contents_end, + lines + label_contents_line_index, n_lines - label_contents_line_index, + _T(' '), &def->label, &def->label_size)); + def->label_needs_free = TRUE; } else { + def->label = (CHAR*) STR(label_contents_beg); + def->label_size = label_contents_end - label_contents_beg; + } + + if(title_is_multiline) { MD_CHECK(md_merge_lines_alloc(ctx, title_contents_beg, title_contents_end, lines + title_contents_line_index, n_lines - title_contents_line_index, _T('\n'), &def->title, &def->title_size)); def->title_needs_free = TRUE; + } else { + def->title = (CHAR*) STR(title_contents_beg); + def->title_size = title_contents_end - title_contents_beg; } + def->dest_beg = dest_contents_beg; + def->dest_end = dest_contents_end; + /* Success. */ ctx->n_ref_defs++; return line_index + 1; abort: /* Failure. */ - if(label_needs_free) - free(label); - return -1; -} - -static inline int -md_is_link_reference_definition(MD_CTX* ctx, const MD_LINE* lines, int n_lines) -{ - int ret; - ret = md_is_link_reference_definition_helper(ctx, lines, n_lines, md_is_link_destination_A); - if(ret == 0) - ret = md_is_link_reference_definition_helper(ctx, lines, n_lines, md_is_link_destination_B); + if(def != NULL && def->label_needs_free) + free(def->label); + if(def != NULL && def->title_needs_free) + free(def->title); return ret; } @@ -2221,7 +2234,7 @@ md_is_link_reference(MD_CTX* ctx, const MD_LINE* lines, int n_lines, { const MD_REF_DEF* def; const MD_LINE* beg_line; - const MD_LINE* end_line; + int is_multiline; CHAR* label; SZ label_size; int ret; @@ -2233,19 +2246,12 @@ md_is_link_reference(MD_CTX* ctx, const MD_LINE* lines, int n_lines, end--; /* Find lines corresponding to the beg and end positions. */ - MD_ASSERT(lines[0].beg <= beg); - beg_line = lines; - while(beg >= beg_line->end) - beg_line++; - - MD_ASSERT(end <= lines[n_lines-1].end); - end_line = beg_line; - while(end >= end_line->end) - end_line++; + beg_line = md_lookup_line(beg, lines, n_lines); + is_multiline = (end > beg_line->end); - if(beg_line != end_line) { + if(is_multiline) { MD_CHECK(md_merge_lines_alloc(ctx, beg, end, beg_line, - n_lines - (beg_line - lines), _T(' '), &label, &label_size)); + (int)(n_lines - (beg_line - lines)), _T(' '), &label, &label_size)); } else { label = (CHAR*) STR(beg); label_size = end - beg; @@ -2260,7 +2266,7 @@ md_is_link_reference(MD_CTX* ctx, const MD_LINE* lines, int n_lines, attr->title_needs_free = FALSE; } - if(beg_line != end_line) + if(is_multiline) free(label); ret = (def != NULL); @@ -2270,9 +2276,8 @@ abort: } static int -md_is_inline_link_spec_helper(MD_CTX* ctx, const MD_LINE* lines, int n_lines, - OFF beg, OFF* p_end, MD_LINK_ATTR* attr, - int (*is_link_dest_fn)(MD_CTX*, OFF, OFF, OFF*, OFF*, OFF*)) +md_is_inline_link_spec(MD_CTX* ctx, const MD_LINE* lines, int n_lines, + OFF beg, OFF* p_end, MD_LINK_ATTR* attr) { int line_index = 0; int tmp_line_index; @@ -2292,24 +2297,30 @@ md_is_inline_link_spec_helper(MD_CTX* ctx, const MD_LINE* lines, int n_lines, /* Optional white space with up to one line break. */ while(off < lines[line_index].end && ISWHITESPACE(off)) off++; - if(off >= lines[line_index].end && ISNEWLINE(off)) { + if(off >= lines[line_index].end && (off >= ctx->size || ISNEWLINE(off))) { line_index++; if(line_index >= n_lines) return FALSE; off = lines[line_index].beg; } - /* (Optional) link destination. */ - if(!is_link_dest_fn(ctx, off, lines[line_index].end, - &off, &attr->dest_beg, &attr->dest_end)) { - if(is_link_dest_fn == md_is_link_destination_B) { - attr->dest_beg = off; - attr->dest_end = off; - } else { - return FALSE; - } + /* Link destination may be omitted, but only when not also having a title. */ + if(off < ctx->size && CH(off) == _T(')')) { + attr->dest_beg = off; + attr->dest_end = off; + attr->title = NULL; + attr->title_size = 0; + attr->title_needs_free = FALSE; + off++; + *p_end = off; + return TRUE; } + /* Link destination. */ + if(!md_is_link_destination(ctx, off, lines[line_index].end, + &off, &attr->dest_beg, &attr->dest_end)) + return FALSE; + /* (Optional) title. */ if(md_is_link_title(ctx, lines + line_index, n_lines - line_index, off, &off, &title_contents_line_index, &tmp_line_index, @@ -2329,7 +2340,7 @@ md_is_inline_link_spec_helper(MD_CTX* ctx, const MD_LINE* lines, int n_lines, /* Optional whitespace followed with final ')'. */ while(off < lines[line_index].end && ISWHITESPACE(off)) off++; - if(off >= lines[line_index].end && ISNEWLINE(off)) { + if (off >= lines[line_index].end && (off >= ctx->size || ISNEWLINE(off))) { line_index++; if(line_index >= n_lines) return FALSE; @@ -2361,14 +2372,6 @@ abort: return ret; } -static inline int -md_is_inline_link_spec(MD_CTX* ctx, const MD_LINE* lines, int n_lines, - OFF beg, OFF* p_end, MD_LINK_ATTR* attr) -{ - return md_is_inline_link_spec_helper(ctx, lines, n_lines, beg, p_end, attr, md_is_link_destination_A) || - md_is_inline_link_spec_helper(ctx, lines, n_lines, beg, p_end, attr, md_is_link_destination_B); -} - static void md_free_ref_defs(MD_CTX* ctx) { @@ -2478,12 +2481,44 @@ struct MD_MARK_tag { /* Mark flags specific for various mark types (so they can share bits). */ #define MD_MARK_EMPH_INTRAWORD 0x20 /* Helper for the "rule of 3". */ -#define MD_MARK_EMPH_MODULO3_0 0x40 -#define MD_MARK_EMPH_MODULO3_1 0x80 -#define MD_MARK_EMPH_MODULO3_2 (0x40 | 0x80) -#define MD_MARK_EMPH_MODULO3_MASK (0x40 | 0x80) +#define MD_MARK_EMPH_MOD3_0 0x40 +#define MD_MARK_EMPH_MOD3_1 0x80 +#define MD_MARK_EMPH_MOD3_2 (0x40 | 0x80) +#define MD_MARK_EMPH_MOD3_MASK (0x40 | 0x80) #define MD_MARK_AUTOLINK 0x20 /* Distinguisher for '<', '>'. */ +#define MD_MARK_VALIDPERMISSIVEAUTOLINK 0x20 /* For permissive autolinks. */ +#define MD_MARK_HASNESTEDBRACKETS 0x20 /* For '[' to rule out invalid link labels early */ + +static MD_MARKCHAIN* +md_asterisk_chain(MD_CTX* ctx, unsigned flags) +{ + switch(flags & (MD_MARK_EMPH_INTRAWORD | MD_MARK_EMPH_MOD3_MASK)) { + case MD_MARK_EMPH_INTRAWORD | MD_MARK_EMPH_MOD3_0: return &ASTERISK_OPENERS_intraword_mod3_0; + case MD_MARK_EMPH_INTRAWORD | MD_MARK_EMPH_MOD3_1: return &ASTERISK_OPENERS_intraword_mod3_1; + case MD_MARK_EMPH_INTRAWORD | MD_MARK_EMPH_MOD3_2: return &ASTERISK_OPENERS_intraword_mod3_2; + case MD_MARK_EMPH_MOD3_0: return &ASTERISK_OPENERS_extraword_mod3_0; + case MD_MARK_EMPH_MOD3_1: return &ASTERISK_OPENERS_extraword_mod3_1; + case MD_MARK_EMPH_MOD3_2: return &ASTERISK_OPENERS_extraword_mod3_2; + default: MD_UNREACHABLE(); + } + return NULL; +} + +static MD_MARKCHAIN* +md_mark_chain(MD_CTX* ctx, int mark_index) +{ + MD_MARK* mark = &ctx->marks[mark_index]; + switch(mark->ch) { + case _T('*'): return md_asterisk_chain(ctx, mark->flags); + case _T('_'): return &UNDERSCORE_OPENERS; + case _T('~'): return (mark->end - mark->beg == 1) ? &TILDE_OPENERS_1 : &TILDE_OPENERS_2; + case _T('!'): MD_FALLTHROUGH(); + case _T('['): return &BRACKET_OPENERS; + case _T('|'): return &TABLECELLBOUNDARIES; + default: return NULL; + } +} static MD_MARK* md_push_mark(MD_CTX* ctx) @@ -2491,7 +2526,9 @@ md_push_mark(MD_CTX* ctx) if(ctx->n_marks >= ctx->alloc_marks) { MD_MARK* new_marks; - ctx->alloc_marks = (ctx->alloc_marks > 0 ? ctx->alloc_marks * 2 : 64); + ctx->alloc_marks = (ctx->alloc_marks > 0 + ? ctx->alloc_marks + ctx->alloc_marks / 2 + : 64); new_marks = realloc(ctx->marks, ctx->alloc_marks * sizeof(MD_MARK)); if(new_marks == NULL) { MD_LOG("realloc() failed."); @@ -2534,6 +2571,7 @@ md_mark_chain_append(MD_CTX* ctx, MD_MARKCHAIN* chain, int mark_index) chain->head = mark_index; ctx->marks[mark_index].prev = chain->tail; + ctx->marks[mark_index].next = -1; chain->tail = mark_index; } @@ -2612,13 +2650,15 @@ md_rollback(MD_CTX* ctx, int opener_index, int closer_index, int how) int i; int mark_index; - /* Cut all unresolved openers at the mark index. - * (start at 1 to not touch PTR_CHAIN.) */ - for(i = 1; i < SIZEOF_ARRAY(ctx->mark_chains); i++) { + /* Cut all unresolved openers at the mark index. */ + for(i = OPENERS_CHAIN_FIRST; i < OPENERS_CHAIN_LAST+1; i++) { MD_MARKCHAIN* chain = &ctx->mark_chains[i]; - while(chain->tail >= opener_index) + while(chain->tail >= opener_index) { + int same = chain->tail == opener_index; chain->tail = ctx->marks[chain->tail].prev; + if (same) break; + } if(chain->tail >= 0) ctx->marks[chain->tail].next = -1; @@ -2626,7 +2666,7 @@ md_rollback(MD_CTX* ctx, int opener_index, int closer_index, int how) chain->head = -1; } - /* Go backwards so that un-resolved openers are re-added into their + /* Go backwards so that unresolved openers are re-added into their * respective chains, in the right order. */ mark_index = closer_index - 1; while(mark_index > opener_index) { @@ -2643,24 +2683,22 @@ md_rollback(MD_CTX* ctx, int opener_index, int closer_index, int how) MD_MARKCHAIN* chain; mark_opener->flags &= ~(MD_MARK_OPENER | MD_MARK_CLOSER | MD_MARK_RESOLVED); - - switch(mark_opener->ch) { - case '*': chain = &ASTERISK_OPENERS; break; - case '_': chain = &UNDERSCORE_OPENERS; break; - case '`': chain = &BACKTICK_OPENERS; break; - case '<': chain = &LOWERTHEN_OPENERS; break; - case '~': chain = &TILDE_OPENERS; break; - default: MD_UNREACHABLE(); break; + chain = md_mark_chain(ctx, opener_index); + if(chain != NULL) { + md_mark_chain_append(ctx, chain, mark_opener_index); + discard_flag = 1; } - md_mark_chain_append(ctx, chain, mark_opener_index); - - discard_flag = 1; } } /* And reset our flags. */ - if(discard_flag) + if(discard_flag) { + /* Make zero-length closer a dummy mark as that's how it was born */ + if((mark->flags & MD_MARK_CLOSER) && mark->beg == mark->end) + mark->ch = 'D'; + mark->flags &= ~(MD_MARK_OPENER | MD_MARK_CLOSER | MD_MARK_RESOLVED); + } /* Jump as far as we can over unresolved or non-interesting marks. */ switch(how) { @@ -2671,7 +2709,7 @@ md_rollback(MD_CTX* ctx, int opener_index, int closer_index, int how) mark_index = mark->prev; break; } - /* Pass through. */ + MD_FALLTHROUGH(); default: mark_index--; break; @@ -2697,132 +2735,359 @@ md_build_mark_char_map(MD_CTX* ctx) ctx->mark_char_map[']'] = 1; ctx->mark_char_map['\0'] = 1; - if(ctx->r.flags & MD_FLAG_STRIKETHROUGH) + if(ctx->parser.flags & MD_FLAG_STRIKETHROUGH) ctx->mark_char_map['~'] = 1; - if(ctx->r.flags & MD_FLAG_PERMISSIVEEMAILAUTOLINKS) + if(ctx->parser.flags & MD_FLAG_LATEXMATHSPANS) + ctx->mark_char_map['$'] = 1; + + if(ctx->parser.flags & MD_FLAG_PERMISSIVEEMAILAUTOLINKS) ctx->mark_char_map['@'] = 1; - if(ctx->r.flags & MD_FLAG_PERMISSIVEURLAUTOLINKS) + if(ctx->parser.flags & MD_FLAG_PERMISSIVEURLAUTOLINKS) ctx->mark_char_map[':'] = 1; - if(ctx->r.flags & MD_FLAG_PERMISSIVEWWWAUTOLINKS) + if(ctx->parser.flags & MD_FLAG_PERMISSIVEWWWAUTOLINKS) ctx->mark_char_map['.'] = 1; - if(ctx->r.flags & MD_FLAG_TABLES) + if((ctx->parser.flags & MD_FLAG_TABLES) || (ctx->parser.flags & MD_FLAG_WIKILINKS)) ctx->mark_char_map['|'] = 1; - if(ctx->r.flags & MD_FLAG_COLLAPSEWHITESPACE) { + if(ctx->parser.flags & MD_FLAG_COLLAPSEWHITESPACE) { int i; - for(i = 0; i < sizeof(ctx->mark_char_map); i++) { + for(i = 0; i < (int) sizeof(ctx->mark_char_map); i++) { if(ISWHITESPACE_(i)) ctx->mark_char_map[i] = 1; } } } +/* We limit code span marks to lower than 32 backticks. This solves the + * pathologic case of too many openers, each of different length: Their + * resolving would be then O(n^2). */ +#define CODESPAN_MARK_MAXLEN 32 + static int -md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) +md_is_code_span(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, + OFF* p_opener_beg, OFF* p_opener_end, + OFF* p_closer_beg, OFF* p_closer_end, + OFF last_potential_closers[CODESPAN_MARK_MAXLEN], + int* p_reached_paragraph_end) { - int i; - int ret = 0; - MD_MARK* mark; + OFF opener_beg = beg; + OFF opener_end; + OFF closer_beg; + OFF closer_end; + SZ mark_len; + OFF line_end; + int has_space_after_opener = FALSE; + int has_eol_after_opener = FALSE; + int has_space_before_closer = FALSE; + int has_eol_before_closer = FALSE; + int has_only_space = TRUE; + int line_index = 0; - for(i = 0; i < n_lines; i++) { - const MD_LINE* line = &lines[i]; - OFF off = line->beg; - OFF line_end = line->end; + line_end = lines[0].end; + opener_end = opener_beg; + while(opener_end < line_end && CH(opener_end) == _T('`')) + opener_end++; + has_space_after_opener = (opener_end < line_end && CH(opener_end) == _T(' ')); + has_eol_after_opener = (opener_end == line_end); - while(TRUE) { - CHAR ch; + /* The caller needs to know end of the opening mark even if we fail. */ + *p_opener_end = opener_end; -#ifdef MD4C_USE_UTF16 - /* For UTF-16, mark_char_map[] covers only ASCII. */ - #define IS_MARK_CHAR(off) ((CH(off) < SIZEOF_ARRAY(ctx->mark_char_map)) && \ - (ctx->mark_char_map[(unsigned char) CH(off)])) -#else - /* For 8-bit encodings, mark_char_map[] covers all 256 elements. */ - #define IS_MARK_CHAR(off) (ctx->mark_char_map[(unsigned char) CH(off)]) -#endif + mark_len = opener_end - opener_beg; + if(mark_len > CODESPAN_MARK_MAXLEN) + return FALSE; - /* Optimization: Fast path (with some loop unrolling). */ - while(off + 4 < line_end && !IS_MARK_CHAR(off+0) && !IS_MARK_CHAR(off+1) - && !IS_MARK_CHAR(off+2) && !IS_MARK_CHAR(off+3)) - off += 4; - while(off < line_end && !IS_MARK_CHAR(off+0)) - off++; + /* Check whether we already know there is no closer of this length. + * If so, re-scan does no sense. This fixes issue #59. */ + if(last_potential_closers[mark_len-1] >= lines[n_lines-1].end || + (*p_reached_paragraph_end && last_potential_closers[mark_len-1] < opener_end)) + return FALSE; - if(off >= line_end) - break; + closer_beg = opener_end; + closer_end = opener_end; - ch = CH(off); + /* Find closer mark. */ + while(TRUE) { + while(closer_beg < line_end && CH(closer_beg) != _T('`')) { + if(CH(closer_beg) != _T(' ')) + has_only_space = FALSE; + closer_beg++; + } + closer_end = closer_beg; + while(closer_end < line_end && CH(closer_end) == _T('`')) + closer_end++; - /* A backslash escape. - * It can go beyond line->end as it may involve escaped new - * line to form a hard break. */ - if(ch == _T('\\') && off+1 < ctx->size && (ISPUNCT(off+1) || ISNEWLINE(off+1))) { - /* Hard-break cannot be on the last line of the block. */ - if(!ISNEWLINE(off+1) || i+1 < n_lines) - PUSH_MARK(ch, off, off+2, MD_MARK_RESOLVED); + if(closer_end - closer_beg == mark_len) { + /* Success. */ + has_space_before_closer = (closer_beg > lines[line_index].beg && CH(closer_beg-1) == _T(' ')); + has_eol_before_closer = (closer_beg == lines[line_index].beg); + break; + } - /* If '`' or '>' follows, we need both marks as the backslash - * may be inside a code span or an autolink where escaping is - * disabled. */ - if(CH(off+1) == _T('`') || CH(off+1) == _T('>')) - off++; - else - off += 2; - continue; - } + if(closer_end - closer_beg > 0) { + /* We have found a back-tick which is not part of the closer. */ + has_only_space = FALSE; - /* A potential (string) emphasis start/end. */ - if(ch == _T('*') || ch == _T('_')) { - OFF tmp = off+1; - int left_level; /* What precedes: 0 = whitespace; 1 = punctuation; 2 = other char. */ - int right_level; /* What follows: 0 = whitespace; 1 = punctuation; 2 = other char. */ + /* But if we eventually fail, remember it as a potential closer + * of its own length for future attempts. This mitigates needs for + * rescans. */ + if(closer_end - closer_beg < CODESPAN_MARK_MAXLEN) { + if(closer_beg > last_potential_closers[closer_end - closer_beg - 1]) + last_potential_closers[closer_end - closer_beg - 1] = closer_beg; + } + } - while(tmp < line_end && CH(tmp) == ch) - tmp++; + if(closer_end >= line_end) { + line_index++; + if(line_index >= n_lines) { + /* Reached end of the paragraph and still nothing. */ + *p_reached_paragraph_end = TRUE; + return FALSE; + } + /* Try on the next line. */ + line_end = lines[line_index].end; + closer_beg = lines[line_index].beg; + } else { + closer_beg = closer_end; + } + } - if(off == line->beg || ISUNICODEWHITESPACEBEFORE(off)) - left_level = 0; - else if(ISUNICODEPUNCTBEFORE(off)) - left_level = 1; - else - left_level = 2; + /* If there is a space or a new line both after and before the opener + * (and if the code span is not made of spaces only), consume one initial + * and one trailing space as part of the marks. */ + if(!has_only_space && + (has_space_after_opener || has_eol_after_opener) && + (has_space_before_closer || has_eol_before_closer)) + { + if(has_space_after_opener) + opener_end++; + else + opener_end = lines[1].beg; + + if(has_space_before_closer) + closer_beg--; + else { + closer_beg = lines[line_index-1].end; + /* We need to eat the preceding "\r\n" but not any line trailing + * spaces. */ + while(closer_beg < ctx->size && ISBLANK(closer_beg)) + closer_beg++; + } + } - if(tmp == line_end || ISUNICODEWHITESPACE(tmp)) - right_level = 0; - else if(ISUNICODEPUNCT(tmp)) - right_level = 1; - else - right_level = 2; + *p_opener_beg = opener_beg; + *p_opener_end = opener_end; + *p_closer_beg = closer_beg; + *p_closer_end = closer_end; + return TRUE; +} - /* Intra-word underscore doesn't have special meaning. */ - if(ch == _T('_') && left_level == 2 && right_level == 2) { - left_level = 0; - right_level = 0; - } +static int +md_is_autolink_uri(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end) +{ + OFF off = beg+1; - if(left_level != 0 || right_level != 0) { - unsigned flags = 0; + MD_ASSERT(CH(beg) == _T('<')); - if(left_level > 0 && left_level >= right_level) - flags |= MD_MARK_POTENTIAL_CLOSER; - if(right_level > 0 && right_level >= left_level) - flags |= MD_MARK_POTENTIAL_OPENER; - if(left_level == 2 && right_level == 2) - flags |= MD_MARK_EMPH_INTRAWORD; + /* Check for scheme. */ + if(off >= max_end || !ISASCII(off)) + return FALSE; + off++; + while(1) { + if(off >= max_end) + return FALSE; + if(off - beg > 32) + return FALSE; + if(CH(off) == _T(':') && off - beg >= 3) + break; + if(!ISALNUM(off) && CH(off) != _T('+') && CH(off) != _T('-') && CH(off) != _T('.')) + return FALSE; + off++; + } + + /* Check the path after the scheme. */ + while(off < max_end && CH(off) != _T('>')) { + if(ISWHITESPACE(off) || ISCNTRL(off) || CH(off) == _T('<')) + return FALSE; + off++; + } + + if(off >= max_end) + return FALSE; + + MD_ASSERT(CH(off) == _T('>')); + *p_end = off+1; + return TRUE; +} + +static int +md_is_autolink_email(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end) +{ + OFF off = beg + 1; + int label_len; + + MD_ASSERT(CH(beg) == _T('<')); + + /* The code should correspond to this regexp: + /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+ + @[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])? + (?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ + */ + + /* Username (before '@'). */ + while(off < max_end && (ISALNUM(off) || ISANYOF(off, _T(".!#$%&'*+/=?^_`{|}~-")))) + off++; + if(off <= beg+1) + return FALSE; + + /* '@' */ + if(off >= max_end || CH(off) != _T('@')) + return FALSE; + off++; + + /* Labels delimited with '.'; each label is sequence of 1 - 63 alnum + * characters or '-', but '-' is not allowed as first or last char. */ + label_len = 0; + while(off < max_end) { + if(ISALNUM(off)) + label_len++; + else if(CH(off) == _T('-') && label_len > 0) + label_len++; + else if(CH(off) == _T('.') && label_len > 0 && CH(off-1) != _T('-')) + label_len = 0; + else + break; + + if(label_len > 63) + return FALSE; + + off++; + } + + if(label_len <= 0 || off >= max_end || CH(off) != _T('>') || CH(off-1) == _T('-')) + return FALSE; + + *p_end = off+1; + return TRUE; +} + +static int +md_is_autolink(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end, int* p_missing_mailto) +{ + if(md_is_autolink_uri(ctx, beg, max_end, p_end)) { + *p_missing_mailto = FALSE; + return TRUE; + } + + if(md_is_autolink_email(ctx, beg, max_end, p_end)) { + *p_missing_mailto = TRUE; + return TRUE; + } + + return FALSE; +} + +static int +md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) +{ + const MD_LINE* line_term = lines + n_lines; + const MD_LINE* line; + int ret = 0; + MD_MARK* mark; + OFF codespan_last_potential_closers[CODESPAN_MARK_MAXLEN] = { 0 }; + int codespan_scanned_till_paragraph_end = FALSE; + + for(line = lines; line < line_term; line++) { + OFF off = line->beg; + OFF line_end = line->end; + + while(TRUE) { + CHAR ch; + +#ifdef MD4C_USE_UTF16 + /* For UTF-16, mark_char_map[] covers only ASCII. */ + #define IS_MARK_CHAR(off) ((CH(off) < SIZEOF_ARRAY(ctx->mark_char_map)) && \ + (ctx->mark_char_map[(unsigned char) CH(off)])) +#else + /* For 8-bit encodings, mark_char_map[] covers all 256 elements. */ + #define IS_MARK_CHAR(off) (ctx->mark_char_map[(unsigned char) CH(off)]) +#endif + + /* Optimization: Use some loop unrolling. */ + while(off + 3 < line_end && !IS_MARK_CHAR(off+0) && !IS_MARK_CHAR(off+1) + && !IS_MARK_CHAR(off+2) && !IS_MARK_CHAR(off+3)) + off += 4; + while(off < line_end && !IS_MARK_CHAR(off+0)) + off++; + + if(off >= line_end) + break; + + ch = CH(off); + + /* A backslash escape. + * It can go beyond line->end as it may involve escaped new + * line to form a hard break. */ + if(ch == _T('\\') && off+1 < ctx->size && (ISPUNCT(off+1) || ISNEWLINE(off+1))) { + /* Hard-break cannot be on the last line of the block. */ + if(!ISNEWLINE(off+1) || line+1 < line_term) + PUSH_MARK(ch, off, off+2, MD_MARK_RESOLVED); + off += 2; + continue; + } + + /* A potential (string) emphasis start/end. */ + if(ch == _T('*') || ch == _T('_')) { + OFF tmp = off+1; + int left_level; /* What precedes: 0 = whitespace; 1 = punctuation; 2 = other char. */ + int right_level; /* What follows: 0 = whitespace; 1 = punctuation; 2 = other char. */ + + while(tmp < line_end && CH(tmp) == ch) + tmp++; + + if(off == line->beg || ISUNICODEWHITESPACEBEFORE(off)) + left_level = 0; + else if(ISUNICODEPUNCTBEFORE(off)) + left_level = 1; + else + left_level = 2; + + if(tmp == line_end || ISUNICODEWHITESPACE(tmp)) + right_level = 0; + else if(ISUNICODEPUNCT(tmp)) + right_level = 1; + else + right_level = 2; + + /* Intra-word underscore doesn't have special meaning. */ + if(ch == _T('_') && left_level == 2 && right_level == 2) { + left_level = 0; + right_level = 0; + } + + if(left_level != 0 || right_level != 0) { + unsigned flags = 0; + + if(left_level > 0 && left_level >= right_level) + flags |= MD_MARK_POTENTIAL_CLOSER; + if(right_level > 0 && right_level >= left_level) + flags |= MD_MARK_POTENTIAL_OPENER; + if(left_level == 2 && right_level == 2) + flags |= MD_MARK_EMPH_INTRAWORD; /* For "the rule of three" we need to remember the original * size of the mark (modulo three), before we potentially * split the mark when being later resolved partially by some * shorter closer. */ switch((tmp - off) % 3) { - case 0: flags |= MD_MARK_EMPH_MODULO3_0; break; - case 1: flags |= MD_MARK_EMPH_MODULO3_1; break; - case 2: flags |= MD_MARK_EMPH_MODULO3_2; break; + case 0: flags |= MD_MARK_EMPH_MOD3_0; break; + case 1: flags |= MD_MARK_EMPH_MOD3_1; break; + case 2: flags |= MD_MARK_EMPH_MOD3_2; break; } PUSH_MARK(ch, off, tmp, flags); @@ -2845,18 +3110,31 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) /* A potential code span start/end. */ if(ch == _T('`')) { - OFF tmp = off+1; - - while(tmp < line_end && CH(tmp) == _T('`')) - tmp++; - - /* We limit code span marks to lower then 256 backticks. This - * solves a pathologic case of too many openers, each of - * different length: Their resolving is then O(n^2). */ - if(tmp - off < 256) - PUSH_MARK(ch, off, tmp, MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER); + OFF opener_beg, opener_end; + OFF closer_beg, closer_end; + int is_code_span; + + is_code_span = md_is_code_span(ctx, line, line_term - line, off, + &opener_beg, &opener_end, &closer_beg, &closer_end, + codespan_last_potential_closers, + &codespan_scanned_till_paragraph_end); + if(is_code_span) { + PUSH_MARK(_T('`'), opener_beg, opener_end, MD_MARK_OPENER | MD_MARK_RESOLVED); + PUSH_MARK(_T('`'), closer_beg, closer_end, MD_MARK_CLOSER | MD_MARK_RESOLVED); + ctx->marks[ctx->n_marks-2].next = ctx->n_marks-1; + ctx->marks[ctx->n_marks-1].prev = ctx->n_marks-2; + + off = closer_end; + + /* Advance the current line accordingly. */ + if(off > line_end) { + line = md_lookup_line(off, line, line_term - line); + line_end = line->end; + } + continue; + } - off = tmp; + off = opener_end; continue; } @@ -2878,9 +3156,48 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) } /* A potential autolink or raw HTML start/end. */ - if(ch == _T('<') || ch == _T('>')) { - if(!(ctx->r.flags & MD_FLAG_NOHTMLSPANS)) - PUSH_MARK(ch, off, off+1, (ch == _T('<') ? MD_MARK_POTENTIAL_OPENER : MD_MARK_POTENTIAL_CLOSER)); + if(ch == _T('<')) { + int is_autolink; + OFF autolink_end; + int missing_mailto; + + if(!(ctx->parser.flags & MD_FLAG_NOHTMLSPANS)) { + int is_html; + OFF html_end; + + /* Given the nature of the raw HTML, we have to recognize + * it here. Doing so later in md_analyze_lt_gt() could + * open can of worms of quadratic complexity. */ + is_html = md_is_html_any(ctx, line, line_term - line, off, + lines[n_lines-1].end, &html_end); + if(is_html) { + PUSH_MARK(_T('<'), off, off, MD_MARK_OPENER | MD_MARK_RESOLVED); + PUSH_MARK(_T('>'), html_end, html_end, MD_MARK_CLOSER | MD_MARK_RESOLVED); + ctx->marks[ctx->n_marks-2].next = ctx->n_marks-1; + ctx->marks[ctx->n_marks-1].prev = ctx->n_marks-2; + off = html_end; + + /* Advance the current line accordingly. */ + if(off > line_end) { + line = md_lookup_line(off, line, line_term - line); + line_end = line->end; + } + continue; + } + } + + is_autolink = md_is_autolink(ctx, off, lines[n_lines-1].end, + &autolink_end, &missing_mailto); + if(is_autolink) { + PUSH_MARK((missing_mailto ? _T('@') : _T('<')), off, off+1, + MD_MARK_OPENER | MD_MARK_RESOLVED | MD_MARK_AUTOLINK); + PUSH_MARK(_T('>'), autolink_end-1, autolink_end, + MD_MARK_CLOSER | MD_MARK_RESOLVED | MD_MARK_AUTOLINK); + ctx->marks[ctx->n_marks-2].next = ctx->n_marks-1; + ctx->marks[ctx->n_marks-1].prev = ctx->n_marks-2; + off = autolink_end; + continue; + } off++; continue; @@ -2932,7 +3249,7 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) }; int scheme_index; - for(scheme_index = 0; scheme_index < SIZEOF_ARRAY(scheme_map); scheme_index++) { + for(scheme_index = 0; scheme_index < (int) SIZEOF_ARRAY(scheme_map); scheme_index++) { const CHAR* scheme = scheme_map[scheme_index].scheme; const SZ scheme_size = scheme_map[scheme_index].scheme_size; const CHAR* suffix = scheme_map[scheme_index].suffix; @@ -2946,7 +3263,7 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) /* Push a dummy as a reserve for a closer. */ PUSH_MARK('D', off, off, 0); off += 1 + suffix_size; - continue; + break; } } @@ -2971,8 +3288,8 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) continue; } - /* A potential table cell boundary. */ - if(table_mode && ch == _T('|')) { + /* A potential table cell boundary or wiki link label delimiter. */ + if((table_mode || ctx->parser.flags & MD_FLAG_WIKILINKS) && ch == _T('|')) { PUSH_MARK(ch, off, off+1, 0); off++; continue; @@ -2985,8 +3302,34 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) while(tmp < line_end && CH(tmp) == _T('~')) tmp++; - PUSH_MARK(ch, off, tmp, MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER); + if(tmp - off < 3) { + unsigned flags = 0; + + if(tmp < line_end && !ISUNICODEWHITESPACE(tmp)) + flags |= MD_MARK_POTENTIAL_OPENER; + if(off > line->beg && !ISUNICODEWHITESPACEBEFORE(off)) + flags |= MD_MARK_POTENTIAL_CLOSER; + if(flags != 0) + PUSH_MARK(ch, off, tmp, flags); + } + off = tmp; + continue; + } + + /* A potential equation start/end */ + if(ch == _T('$')) { + /* We can have at most two consecutive $ signs, + * where two dollar signs signify a display equation. */ + OFF tmp = off+1; + + while(tmp < line_end && CH(tmp) == _T('$')) + tmp++; + + if (tmp - off <= 2) + PUSH_MARK(ch, off, tmp, MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER); + off = tmp; + continue; } /* Turn non-trivial whitespace into single space. */ @@ -3014,239 +3357,14 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) } } - /* Add a dummy mark after the end of processed block to simplify - * md_process_inlines(). */ - PUSH_MARK(127, ctx->size+1, ctx->size+1, MD_MARK_RESOLVED); + /* Add a dummy mark at the end of the mark vector to simplify + * process_inlines(). */ + PUSH_MARK(127, ctx->size, ctx->size, MD_MARK_RESOLVED); abort: return ret; } - -/* Analyze whether the back-tick is really start/end mark of a code span. - * If yes, reset all marks inside of it and setup flags of both marks. */ -static void -md_analyze_backtick(MD_CTX* ctx, int mark_index) -{ - MD_MARK* mark = &ctx->marks[mark_index]; - int opener_index = BACKTICK_OPENERS.tail; - - /* Try to find unresolved opener of the same length. If we find it, - * we form a code span. */ - while(opener_index >= 0) { - MD_MARK* opener = &ctx->marks[opener_index]; - - if(opener->end - opener->beg == mark->end - mark->beg) { - /* Rollback anything found inside it. - * (e.g. the code span contains some back-ticks or other special - * chars we misinterpreted.) */ - md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_ALL); - - /* Resolve the span. */ - md_resolve_range(ctx, &BACKTICK_OPENERS, opener_index, mark_index); - - /* Append any space or new line inside the span into the mark - * itself to swallow it. */ - while(CH(opener->end) == _T(' ') || ISNEWLINE(opener->end)) - opener->end++; - if(mark->beg > opener->end) { - while(CH(mark->beg-1) == _T(' ') || ISNEWLINE(mark->beg-1)) - mark->beg--; - } - - /* Done. */ - return; - } - - opener_index = ctx->marks[opener_index].prev; - } - - /* We didn't find any matching opener, so we ourselves may be the opener - * of some upcoming closer. We also have to handle specially if there is - * a backslash mark before it as that can cancel the first backtick. */ - if(mark_index > 0 && (mark-1)->beg == mark->beg - 1 && (mark-1)->ch == '\\') { - if(mark->end - mark->beg == 1) { - /* Single escaped backtick. */ - return; - } - - /* Remove the escaped backtick from the opener. */ - mark->beg++; - } - - if(mark->flags & MD_MARK_POTENTIAL_OPENER) - md_mark_chain_append(ctx, &BACKTICK_OPENERS, mark_index); -} - -static int -md_is_autolink_uri(MD_CTX* ctx, OFF beg, OFF end) -{ - OFF off = beg; - - /* Check for scheme. */ - if(off >= end || !ISASCII(off)) - return FALSE; - off++; - while(1) { - if(off >= end) - return FALSE; - if(off - beg > 32) - return FALSE; - if(CH(off) == _T(':') && off - beg >= 2) - break; - if(!ISALNUM(off) && CH(off) != _T('+') && CH(off) != _T('-') && CH(off) != _T('.')) - return FALSE; - off++; - } - - /* Check the path after the scheme. */ - while(off < end) { - if(ISWHITESPACE(off) || ISCNTRL(off) || CH(off) == _T('<') || CH(off) == _T('>')) - return FALSE; - off++; - } - - return TRUE; -} - -static int -md_is_autolink_email(MD_CTX* ctx, OFF beg, OFF end) -{ - OFF off = beg; - int label_len; - - /* The code should correspond to this regexp: - /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+ - @[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])? - (?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ - */ - - /* Username (before '@'). */ - while(off < end && (ISALNUM(off) || ISANYOF(off, _T(".!#$%&'*+/=?^_`{|}~-")))) - off++; - if(off <= beg) - return FALSE; - - /* '@' */ - if(off >= end || CH(off) != _T('@')) - return FALSE; - off++; - - /* Labels delimited with '.'; each label is sequence of 1 - 62 alnum - * characters or '-', but '-' is not allowed as first or last char. */ - label_len = 0; - while(off < end) { - if(ISALNUM(off)) - label_len++; - else if(CH(off) == _T('-') && label_len > 0) - label_len++; - else if(CH(off) == _T('.') && label_len > 0 && CH(off-1) != _T('-')) - label_len = 0; - else - return FALSE; - - if(label_len > 63) - return FALSE; - - off++; - } - - if(label_len <= 0 || CH(off-1) == _T('-')) - return FALSE; - - return TRUE; -} - -static int -md_is_autolink(MD_CTX* ctx, OFF beg, OFF end, int* p_missing_mailto) -{ - MD_ASSERT(CH(beg) == _T('<')); - MD_ASSERT(CH(end-1) == _T('>')); - - beg++; - end--; - - if(md_is_autolink_uri(ctx, beg, end)) - return TRUE; - - if(md_is_autolink_email(ctx, beg, end)) { - *p_missing_mailto = 1; - return TRUE; - } - - return FALSE; -} - -static void -md_analyze_lt_gt(MD_CTX* ctx, int mark_index, const MD_LINE* lines, int n_lines) -{ - MD_MARK* mark = &ctx->marks[mark_index]; - int opener_index; - - /* If it is an opener ('<'), remember it. */ - if(mark->flags & MD_MARK_POTENTIAL_OPENER) { - md_mark_chain_append(ctx, &LOWERTHEN_OPENERS, mark_index); - return; - } - - /* Otherwise we are potential closer and we try to resolve with since all - * the chained unresolved openers. */ - opener_index = LOWERTHEN_OPENERS.head; - while(opener_index >= 0) { - MD_MARK* opener = &ctx->marks[opener_index]; - OFF detected_end; - int is_autolink = 0; - int is_missing_mailto = 0; - int is_raw_html = 0; - - is_autolink = (md_is_autolink(ctx, opener->beg, mark->end, &is_missing_mailto)); - - if(is_autolink) { - if(is_missing_mailto) - opener->ch = _T('@'); - } else { - /* Identify the line where the opening mark lives. */ - int line_index = 0; - while(1) { - if(opener->beg < lines[line_index].end) - break; - line_index++; - } - - is_raw_html = (md_is_html_any(ctx, lines + line_index, - n_lines - line_index, opener->beg, mark->end, &detected_end)); - } - - /* Check whether the range forms a valid raw HTML. */ - if(is_autolink || is_raw_html) { - md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_ALL); - md_resolve_range(ctx, &LOWERTHEN_OPENERS, opener_index, mark_index); - - if(is_raw_html) { - /* If this fails, it means we have missed some earlier opportunity - * to resolve the opener of raw HTML. */ - MD_ASSERT(detected_end == mark->end); - - /* Make these marks zero width so the '<' and '>' are part of its - * contents. */ - opener->end = opener->beg; - mark->beg = mark->end; - - opener->flags &= ~MD_MARK_AUTOLINK; - mark->flags &= ~MD_MARK_AUTOLINK; - } else { - opener->flags |= MD_MARK_AUTOLINK; - mark->flags |= MD_MARK_AUTOLINK; - } - - /* And we are done. */ - return; - } - - opener_index = opener->next; - } -} - static void md_analyze_bracket(MD_CTX* ctx, int mark_index) { @@ -3255,17 +3373,20 @@ md_analyze_bracket(MD_CTX* ctx, int mark_index) * or enclosing pair of brackets (if the inner is the link, the outer * one cannot be.) * - * Therefore we here only construct a list of resolved '[' ']' pairs - * ordered by position of the closer. This allows ur to analyze what is - * or is not link in the right order, from inside to outside in case - * of nested brackets. + * Therefore we here only construct a list of '[' ']' pairs ordered by + * position of the closer. This allows us to analyze what is or is not + * link in the right order, from inside to outside in case of nested + * brackets. * - * The resolving itself is deferred into md_resolve_links(). + * The resolving itself is deferred to md_resolve_links(). */ MD_MARK* mark = &ctx->marks[mark_index]; if(mark->flags & MD_MARK_POTENTIAL_OPENER) { + if(BRACKET_OPENERS.head != -1) + ctx->marks[BRACKET_OPENERS.tail].flags |= MD_MARK_HASNESTEDBRACKETS; + md_mark_chain_append(ctx, &BRACKET_OPENERS, mark_index); return; } @@ -3342,13 +3463,99 @@ md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, int n_lines) continue; } + /* Recognize and resolve wiki links. + * Wiki-links maybe '[[destination]]' or '[[destination|label]]'. + */ + if ((ctx->parser.flags & MD_FLAG_WIKILINKS) && + (opener->end - opener->beg == 1) && /* not image */ + next_opener != NULL && /* double '[' opener */ + next_opener->ch == '[' && + (next_opener->beg == opener->beg - 1) && + (next_opener->end - next_opener->beg == 1) && + next_closer != NULL && /* double ']' closer */ + next_closer->ch == ']' && + (next_closer->beg == closer->beg + 1) && + (next_closer->end - next_closer->beg == 1)) + { + MD_MARK* delim = NULL; + int delim_index; + OFF dest_beg, dest_end; + + is_link = TRUE; + + /* We don't allow destination to be longer than 100 characters. + * Lets scan to see whether there is '|'. (If not then the whole + * wiki-link has to be below the 100 characters.) */ + delim_index = opener_index + 1; + while(delim_index < closer_index) { + MD_MARK* m = &ctx->marks[delim_index]; + if(m->ch == '|') { + delim = m; + break; + } + if(m->ch != 'D' && m->beg - opener->end > 100) + break; + delim_index++; + } + dest_beg = opener->end; + dest_end = (delim != NULL) ? delim->beg : closer->beg; + if(dest_end - dest_beg == 0 || dest_end - dest_beg > 100) + is_link = FALSE; + + /* There may not be any new line in the destination. */ + if(is_link) { + OFF off; + for(off = dest_beg; off < dest_end; off++) { + if(ISNEWLINE(off)) { + is_link = FALSE; + break; + } + } + } + + if(is_link) { + if(delim != NULL) { + if(delim->end < closer->beg) { + md_rollback(ctx, opener_index, delim_index, MD_ROLLBACK_ALL); + md_rollback(ctx, delim_index, closer_index, MD_ROLLBACK_CROSSING); + delim->flags |= MD_MARK_RESOLVED; + opener->end = delim->beg; + } else { + /* The pipe is just before the closer: [[foo|]] */ + md_rollback(ctx, opener_index, closer_index, MD_ROLLBACK_ALL); + closer->beg = delim->beg; + delim = NULL; + } + } + + opener->beg = next_opener->beg; + opener->next = closer_index; + opener->flags |= MD_MARK_OPENER | MD_MARK_RESOLVED; + + closer->end = next_closer->end; + closer->prev = opener_index; + closer->flags |= MD_MARK_CLOSER | MD_MARK_RESOLVED; + + last_link_beg = opener->beg; + last_link_end = closer->end; + + if(delim != NULL) + md_analyze_link_contents(ctx, lines, n_lines, delim_index+1, closer_index); + + opener_index = next_opener->prev; + continue; + } + } + if(next_opener != NULL && next_opener->beg == closer->end) { if(next_closer->beg > closer->end + 1) { /* Might be full reference link. */ - is_link = md_is_link_reference(ctx, lines, n_lines, next_opener->beg, next_closer->end, &attr); + if(!(next_opener->flags & MD_MARK_HASNESTEDBRACKETS)) + is_link = md_is_link_reference(ctx, lines, n_lines, next_opener->beg, next_closer->end, &attr); } else { /* Might be shortcut reference link. */ - is_link = md_is_link_reference(ctx, lines, n_lines, opener->beg, closer->end, &attr); + if(!(opener->flags & MD_MARK_HASNESTEDBRACKETS)) + is_link = md_is_link_reference(ctx, lines, n_lines, opener->beg, closer->end, &attr); } if(is_link < 0) @@ -3357,11 +3564,15 @@ md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, int n_lines) if(is_link) { /* Eat the 2nd "[...]". */ closer->end = next_closer->end; + + /* Do not analyze the label as a standalone link in the next + * iteration. */ + next_index = ctx->marks[next_index].prev; } } else { if(closer->end < ctx->size && CH(closer->end) == _T('(')) { /* Might be inline link. */ - OFF inline_link_end = -1; + OFF inline_link_end = UINT_MAX; is_link = md_is_inline_link_spec(ctx, lines, n_lines, closer->end, &inline_link_end, &attr); if(is_link < 0) @@ -3401,7 +3612,8 @@ md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, int n_lines) if(!is_link) { /* Might be collapsed reference link. */ - is_link = md_is_link_reference(ctx, lines, n_lines, opener->beg, closer->end, &attr); + if(!(opener->flags & MD_MARK_HASNESTEDBRACKETS)) + is_link = md_is_link_reference(ctx, lines, n_lines, opener->beg, closer->end, &attr); if(is_link < 0) return -1; } @@ -3420,6 +3632,7 @@ md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, int n_lines) MD_ASSERT(ctx->marks[opener_index+2].ch == 'D'); md_mark_store_ptr(ctx, opener_index+2, attr.title); + /* The title might or might not have been allocated for us. */ if(attr.title_needs_free) md_mark_chain_append(ctx, &PTR_CHAIN, opener_index+2); ctx->marks[opener_index+2].prev = attr.title_size; @@ -3433,6 +3646,34 @@ md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, int n_lines) } md_analyze_link_contents(ctx, lines, n_lines, opener_index+1, closer_index); + + /* If the link text is formed by nothing but permissive autolink, + * suppress the autolink. + * See https://github.com/mity/md4c/issues/152 for more info. */ + if(ctx->parser.flags & MD_FLAG_PERMISSIVEAUTOLINKS) { + MD_MARK* first_nested; + MD_MARK* last_nested; + + first_nested = opener + 1; + while(first_nested->ch == _T('D') && first_nested < closer) + first_nested++; + + last_nested = closer - 1; + while(first_nested->ch == _T('D') && last_nested > opener) + last_nested--; + + if((first_nested->flags & MD_MARK_RESOLVED) && + first_nested->beg == opener->end && + ISANYOF_(first_nested->ch, _T("@:.")) && + first_nested->next == (last_nested - ctx->marks) && + last_nested->end == closer->beg) + { + first_nested->ch = _T('D'); + first_nested->flags &= ~MD_MARK_RESOLVED; + last_nested->ch = _T('D'); + last_nested->flags &= ~MD_MARK_RESOLVED; + } + } } opener_index = next_index; @@ -3486,10 +3727,10 @@ md_analyze_table_cell_boundary(MD_CTX* ctx, int mark_index) * follows. */ static int -md_split_simple_pairing_mark(MD_CTX* ctx, int mark_index, SZ n) +md_split_emph_mark(MD_CTX* ctx, int mark_index, SZ n) { MD_MARK* mark = &ctx->marks[mark_index]; - int new_mark_index = mark_index + (mark->end - mark->beg - 1); + int new_mark_index = mark_index + (mark->end - mark->beg - n); MD_MARK* dummy = &ctx->marks[new_mark_index]; MD_ASSERT(mark->end - mark->beg > n); @@ -3503,96 +3744,124 @@ md_split_simple_pairing_mark(MD_CTX* ctx, int mark_index, SZ n) } static void -md_analyze_simple_pairing_mark(MD_CTX* ctx, MD_MARKCHAIN* chain, int mark_index, - int apply_rule_of_three) +md_analyze_emph(MD_CTX* ctx, int mark_index) { MD_MARK* mark = &ctx->marks[mark_index]; + MD_MARKCHAIN* chain = md_mark_chain(ctx, mark_index); /* If we can be a closer, try to resolve with the preceding opener. */ - if((mark->flags & MD_MARK_POTENTIAL_CLOSER) && chain->tail >= 0) { - int opener_index = chain->tail; - MD_MARK* opener = &ctx->marks[opener_index]; - SZ opener_size = opener->end - opener->beg; - SZ closer_size = mark->end - mark->beg; - - /* Apply the "rule of three". */ - if(apply_rule_of_three) { - while((mark->flags & MD_MARK_EMPH_INTRAWORD) || (opener->flags & MD_MARK_EMPH_INTRAWORD)) { - SZ opener_orig_size_modulo3; - - switch(opener->flags & MD_MARK_EMPH_MODULO3_MASK) { - case MD_MARK_EMPH_MODULO3_0: opener_orig_size_modulo3 = 0; break; - case MD_MARK_EMPH_MODULO3_1: opener_orig_size_modulo3 = 1; break; - case MD_MARK_EMPH_MODULO3_2: opener_orig_size_modulo3 = 2; break; - default: MD_UNREACHABLE(); break; - } - - if((opener_orig_size_modulo3 + closer_size) % 3 != 0) { - /* This opener is suitable. */ - break; - } - - if(opener->prev >= 0) { - /* Try previous opener. */ - opener_index = opener->prev; - opener = &ctx->marks[opener_index]; - opener_size = opener->end - opener->beg; - closer_size = mark->end - mark->beg; - } else { - /* No suitable opener found. */ - goto cannot_resolve; + if(mark->flags & MD_MARK_POTENTIAL_CLOSER) { + MD_MARK* opener = NULL; + int opener_index = 0; + + if(mark->ch == _T('*')) { + MD_MARKCHAIN* opener_chains[6]; + int i, n_opener_chains; + unsigned flags = mark->flags; + + /* Apply the "rule of three". */ + n_opener_chains = 0; + opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_intraword_mod3_0; + if((flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_2) + opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_intraword_mod3_1; + if((flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_1) + opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_intraword_mod3_2; + opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_extraword_mod3_0; + if(!(flags & MD_MARK_EMPH_INTRAWORD) || (flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_2) + opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_extraword_mod3_1; + if(!(flags & MD_MARK_EMPH_INTRAWORD) || (flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_1) + opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_extraword_mod3_2; + + /* Opener is the most recent mark from the allowed chains. */ + for(i = 0; i < n_opener_chains; i++) { + if(opener_chains[i]->tail >= 0) { + int tmp_index = opener_chains[i]->tail; + MD_MARK* tmp_mark = &ctx->marks[tmp_index]; + if(opener == NULL || tmp_mark->end > opener->end) { + opener_index = tmp_index; + opener = tmp_mark; + } } } + } else { + /* Simple emph. mark */ + if(chain->tail >= 0) { + opener_index = chain->tail; + opener = &ctx->marks[opener_index]; + } } - if(opener_size > closer_size) { - opener_index = md_split_simple_pairing_mark(ctx, opener_index, closer_size); - md_mark_chain_append(ctx, chain, opener_index); - } else if(opener_size < closer_size) { - md_split_simple_pairing_mark(ctx, mark_index, closer_size - opener_size); - } + /* Resolve, if we have found matching opener. */ + if(opener != NULL) { + SZ opener_size = opener->end - opener->beg; + SZ closer_size = mark->end - mark->beg; + MD_MARKCHAIN* opener_chain = md_mark_chain(ctx, opener_index); + + if(opener_size > closer_size) { + opener_index = md_split_emph_mark(ctx, opener_index, closer_size); + md_mark_chain_append(ctx, opener_chain, opener_index); + } else if(opener_size < closer_size) { + md_split_emph_mark(ctx, mark_index, closer_size - opener_size); + } - md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_CROSSING); - md_resolve_range(ctx, chain, opener_index, mark_index); - return; + md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_CROSSING); + md_resolve_range(ctx, opener_chain, opener_index, mark_index); + return; + } } -cannot_resolve: - /* If not resolved, and we can be an opener, remember the mark for - * the future. */ + /* If we could not resolve as closer, we may be yet be an opener. */ if(mark->flags & MD_MARK_POTENTIAL_OPENER) md_mark_chain_append(ctx, chain, mark_index); } -static inline void -md_analyze_asterisk(MD_CTX* ctx, int mark_index) -{ - md_analyze_simple_pairing_mark(ctx, &ASTERISK_OPENERS, mark_index, 1); -} - -static inline void -md_analyze_underscore(MD_CTX* ctx, int mark_index) -{ - md_analyze_simple_pairing_mark(ctx, &UNDERSCORE_OPENERS, mark_index, 1); -} - static void md_analyze_tilde(MD_CTX* ctx, int mark_index) { - /* We attempt to be Github Flavored Markdown compatible here. GFM says - * that length of the tilde sequence is not important at all. Note that - * implies the TILDE_OPENERS chain can have at most one item. */ + MD_MARK* mark = &ctx->marks[mark_index]; + MD_MARKCHAIN* chain = md_mark_chain(ctx, mark_index); + + /* We attempt to be Github Flavored Markdown compatible here. GFM accepts + * only tildes sequences of length 1 and 2, and the length of the opener + * and closer has to match. */ - if(TILDE_OPENERS.head >= 0) { - /* The chain already contains an opener, so we may resolve the span. */ - int opener_index = TILDE_OPENERS.head; + if((mark->flags & MD_MARK_POTENTIAL_CLOSER) && chain->head >= 0) { + int opener_index = chain->head; md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_CROSSING); - md_resolve_range(ctx, &TILDE_OPENERS, opener_index, mark_index); - } else { - /* We can only be opener. */ - md_mark_chain_append(ctx, &TILDE_OPENERS, mark_index); + md_resolve_range(ctx, chain, opener_index, mark_index); + return; + } + + if(mark->flags & MD_MARK_POTENTIAL_OPENER) + md_mark_chain_append(ctx, chain, mark_index); +} + +static void +md_analyze_dollar(MD_CTX* ctx, int mark_index) +{ + /* This should mimic the way inline equations work in LaTeX, so there + * can only ever be one item in the chain (i.e. the dollars can't be + * nested). This is basically the same as the md_analyze_tilde function, + * except that we require matching openers and closers to be of the same + * length. + * + * E.g.: $abc$$def$$ => abc (display equation) def (end equation) */ + if(DOLLAR_OPENERS.head >= 0) { + /* If the potential closer has a non-matching number of $, discard */ + MD_MARK* open = &ctx->marks[DOLLAR_OPENERS.head]; + MD_MARK* close = &ctx->marks[mark_index]; + + int opener_index = DOLLAR_OPENERS.head; + md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_ALL); + if (open->end - open->beg == close->end - close->beg) { + /* We are the matching closer */ + md_resolve_range(ctx, &DOLLAR_OPENERS, opener_index, mark_index); + return; + } } + + md_mark_chain_append(ctx, &DOLLAR_OPENERS, mark_index); } static void @@ -3600,62 +3869,78 @@ md_analyze_permissive_url_autolink(MD_CTX* ctx, int mark_index) { MD_MARK* opener = &ctx->marks[mark_index]; int closer_index = mark_index + 1; - MD_MARK* closer = &ctx->marks[mark_index + 1]; + MD_MARK* closer = &ctx->marks[closer_index]; MD_MARK* next_resolved_mark; OFF off = opener->end; - int seen_dot = FALSE; - int seen_underscore_or_hyphen[2] = { FALSE, FALSE }; + int n_dots = FALSE; + int has_underscore_in_last_seg = FALSE; + int has_underscore_in_next_to_last_seg = FALSE; + int n_opened_parenthesis = 0; + int n_excess_parenthesis = 0; /* Check for domain. */ while(off < ctx->size) { - if(ISALNUM(off)) { + if(ISALNUM(off) || CH(off) == _T('-')) { off++; } else if(CH(off) == _T('.')) { - seen_dot = TRUE; - seen_underscore_or_hyphen[0] = seen_underscore_or_hyphen[1]; - seen_underscore_or_hyphen[1] = FALSE; + /* We must see at least one period. */ + n_dots++; + has_underscore_in_next_to_last_seg = has_underscore_in_last_seg; + has_underscore_in_last_seg = FALSE; off++; - } else if(ISANYOF2(off, _T('-'), _T('_'))) { - seen_underscore_or_hyphen[1] = TRUE; + } else if(CH(off) == _T('_')) { + /* No underscore may be present in the last two domain segments. */ + has_underscore_in_last_seg = TRUE; off++; } else { break; } } - - if(off <= opener->end || !seen_dot || seen_underscore_or_hyphen[0] || seen_underscore_or_hyphen[1]) + if(off > opener->end && CH(off-1) == _T('.')) { + off--; + n_dots--; + } + if(off <= opener->end || n_dots == 0 || has_underscore_in_next_to_last_seg || has_underscore_in_last_seg) return; /* Check for path. */ next_resolved_mark = closer + 1; while(next_resolved_mark->ch == 'D' || !(next_resolved_mark->flags & MD_MARK_RESOLVED)) next_resolved_mark++; - while(off < next_resolved_mark->beg && CH(off) != _T('<') && !ISWHITESPACE(off) && !ISNEWLINE(off)) + while(off < next_resolved_mark->beg && CH(off) != _T('<') && !ISWHITESPACE(off) && !ISNEWLINE(off)) { + /* Parenthesis must be balanced. */ + if(CH(off) == _T('(')) { + n_opened_parenthesis++; + } else if(CH(off) == _T(')')) { + if(n_opened_parenthesis > 0) + n_opened_parenthesis--; + else + n_excess_parenthesis++; + } + off++; + } - /* Path validation. */ - if(ISANYOF(off-1, _T("?!.,:*_~)"))) { - if(CH(off-1) != _T(')')) { + /* Trim a trailing punctuation from the end. */ + while(TRUE) { + if(ISANYOF(off-1, _T("?!.,:*_~"))) { + off--; + } else if(CH(off-1) == ')' && n_excess_parenthesis > 0) { + /* Unmatched ')' can be in an interior of the path but not at the + * of it, so the auto-link may be safely nested in a parenthesis + * pair. */ off--; + n_excess_parenthesis--; } else { - int parenthesis_balance = 0; - OFF tmp; - - for(tmp = opener->end; tmp < off; tmp++) { - if(CH(tmp) == _T('(')) - parenthesis_balance++; - else if(CH(tmp) == _T(')')) - parenthesis_balance--; - } - - if(parenthesis_balance < 0) - off--; + break; } } - /* Ok. Lets call it auto-link. Adapt opener and create closer to zero + /* Ok. Lets call it an auto-link. Adapt opener and create closer to zero * length so all the contents becomes the link text. */ - MD_ASSERT(closer->ch == 'D'); + MD_ASSERT(closer->ch == 'D' || + ((ctx->parser.flags & MD_FLAG_PERMISSIVEWWWAUTOLINKS) && + (closer->ch == '.' || closer->ch == ':' || closer->ch == '@'))); opener->end = opener->beg; closer->ch = opener->ch; closer->beg = off; @@ -3677,7 +3962,7 @@ md_analyze_permissive_email_autolink(MD_CTX* ctx, int mark_index) OFF end = opener->end; int dot_count = 0; - MD_ASSERT(CH(beg) == _T('@')); + MD_ASSERT(opener->ch == _T('@')); /* Scan for name before '@'. */ while(beg > 0 && (ISALNUM(beg-1) || ISANYOF(beg-1, _T(".-_+")))) @@ -3702,7 +3987,7 @@ md_analyze_permissive_email_autolink(MD_CTX* ctx, int mark_index) * length so all the contents becomes the link text. */ closer_index = mark_index + 1; closer = &ctx->marks[closer_index]; - MD_ASSERT(closer->ch == 'D'); + if (closer->ch != 'D') return; opener->beg = beg; opener->end = beg; @@ -3717,6 +4002,8 @@ md_analyze_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int mark_beg, int mark_end, const CHAR* mark_chars) { int i = mark_beg; + MD_UNUSED(lines); + MD_UNUSED(n_lines); while(i < mark_end) { MD_MARK* mark = &ctx->marks[i]; @@ -3740,17 +4027,15 @@ md_analyze_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, /* Analyze the mark. */ switch(mark->ch) { - case '`': md_analyze_backtick(ctx, i); break; - case '<': /* Pass through. */ - case '>': md_analyze_lt_gt(ctx, i, lines, n_lines); break; case '[': /* Pass through. */ case '!': /* Pass through. */ case ']': md_analyze_bracket(ctx, i); break; case '&': md_analyze_entity(ctx, i); break; case '|': md_analyze_table_cell_boundary(ctx, i); break; - case '*': md_analyze_asterisk(ctx, i); break; - case '_': md_analyze_underscore(ctx, i); break; + case '_': /* Pass through. */ + case '*': md_analyze_emph(ctx, i); break; case '~': md_analyze_tilde(ctx, i); break; + case '$': md_analyze_dollar(ctx, i); break; case '.': /* Pass through. */ case ':': md_analyze_permissive_url_autolink(ctx, i); break; case '@': md_analyze_permissive_email_autolink(ctx, i); break; @@ -3770,25 +4055,18 @@ md_analyze_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mod ctx->n_marks = 0; /* Collect all marks. */ - if(md_collect_marks(ctx, lines, n_lines, table_mode) != 0) - return -1; + MD_CHECK(md_collect_marks(ctx, lines, n_lines, table_mode)); - /* We analyze marks in few groups to handle their precedence. */ - /* (1) Entities; code spans; autolinks; raw HTML. */ - md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("&`<>")); - BACKTICK_OPENERS.head = -1; - BACKTICK_OPENERS.tail = -1; - LOWERTHEN_OPENERS.head = -1; - LOWERTHEN_OPENERS.tail = -1; - /* (2) Links. */ + /* (1) Links. */ md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("[]!")); MD_CHECK(md_resolve_links(ctx, lines, n_lines)); BRACKET_OPENERS.head = -1; BRACKET_OPENERS.tail = -1; ctx->unresolved_link_head = -1; ctx->unresolved_link_tail = -1; + if(table_mode) { - /* (3a) Analyze table cell boundaries. + /* (2) Analyze table cell boundaries. * Note we reset TABLECELLBOUNDARIES chain prior to the call md_analyze_marks(), * not after, because caller may need it. */ MD_ASSERT(n_lines == 1); @@ -3796,11 +4074,12 @@ md_analyze_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mod TABLECELLBOUNDARIES.tail = -1; ctx->n_table_cell_boundaries = 0; md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("|")); - } else { - /* (3b) Emphasis and strong emphasis; permissive autolinks. */ - md_analyze_link_contents(ctx, lines, n_lines, 0, ctx->n_marks); + return ret; } + /* (3) Emphasis and strong emphasis; permissive autolinks. */ + md_analyze_link_contents(ctx, lines, n_lines, 0, ctx->n_marks); + abort: return ret; } @@ -3809,13 +4088,15 @@ static void md_analyze_link_contents(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int mark_beg, int mark_end) { - md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, _T("*_~@:.")); - ASTERISK_OPENERS.head = -1; - ASTERISK_OPENERS.tail = -1; - UNDERSCORE_OPENERS.head = -1; - UNDERSCORE_OPENERS.tail = -1; - TILDE_OPENERS.head = -1; - TILDE_OPENERS.tail = -1; + int i; + + md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, _T("&")); + md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, _T("*_~$@:.")); + + for(i = OPENERS_CHAIN_FIRST; i <= OPENERS_CHAIN_LAST; i++) { + ctx->mark_chains[i].head = -1; + ctx->mark_chains[i].tail = -1; + } } static int @@ -3823,8 +4104,8 @@ md_enter_leave_span_a(MD_CTX* ctx, int enter, MD_SPANTYPE type, const CHAR* dest, SZ dest_size, int prohibit_escapes_in_dest, const CHAR* title, SZ title_size) { - MD_ATTRIBUTE_BUILD href_build; - MD_ATTRIBUTE_BUILD title_build; + MD_ATTRIBUTE_BUILD href_build = { 0 }; + MD_ATTRIBUTE_BUILD title_build = { 0 }; MD_SPAN_A_DETAIL det; int ret = 0; @@ -3847,6 +4128,27 @@ abort: return ret; } +static int +md_enter_leave_span_wikilink(MD_CTX* ctx, int enter, const CHAR* target, SZ target_size) +{ + MD_ATTRIBUTE_BUILD target_build = { 0 }; + MD_SPAN_WIKILINK_DETAIL det; + int ret = 0; + + memset(&det, 0, sizeof(MD_SPAN_WIKILINK_DETAIL)); + MD_CHECK(md_build_attribute(ctx, target, target_size, 0, &det.target, &target_build)); + + if (enter) + MD_ENTER_SPAN(MD_SPAN_WIKILINK, &det); + else + MD_LEAVE_SPAN(MD_SPAN_WIKILINK, &det); + +abort: + md_free_attribute(ctx, &target_build); + return ret; +} + + /* Render the output, accordingly to the analyzed ctx->marks. */ static int md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines) @@ -3902,7 +4204,39 @@ md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines) } break; - case '_': + case '_': /* Underline (or emphasis if we fall through). */ + if(ctx->parser.flags & MD_FLAG_UNDERLINE) { + if(mark->flags & MD_MARK_OPENER) { + /* while(off < mark->end) { */ + /* MD_ENTER_SPAN(MD_SPAN_U, NULL); */ + /* off++; */ + /* } */ + if((mark->end - off) % 2) { + MD_ENTER_SPAN(MD_SPAN_U, NULL); + off++; + } + while(off + 1 < mark->end) { + MD_ENTER_SPAN(MD_SPAN_STRONG, NULL); + off += 2; + } + } else { + /* while(off < mark->end) { */ + /* MD_LEAVE_SPAN(MD_SPAN_U, NULL); */ + /* off++; */ + /* } */ + while(off + 1 < mark->end) { + MD_LEAVE_SPAN(MD_SPAN_STRONG, NULL); + off += 2; + } + if((mark->end - off) % 2) { + MD_LEAVE_SPAN(MD_SPAN_U, NULL); + off++; + } + } + break; + } + MD_FALLTHROUGH(); + case '*': /* Emphasis, strong emphasis. */ if(mark->flags & MD_MARK_OPENER) { if((mark->end - off) % 2) { @@ -3932,21 +4266,61 @@ md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines) MD_LEAVE_SPAN(MD_SPAN_DEL, NULL); break; - case '[': /* Link, image. */ + case '$': + if(mark->flags & MD_MARK_OPENER) { + MD_ENTER_SPAN((mark->end - off) % 2 ? MD_SPAN_LATEXMATH : MD_SPAN_LATEXMATH_DISPLAY, NULL); + text_type = MD_TEXT_LATEXMATH; + } else { + MD_LEAVE_SPAN((mark->end - off) % 2 ? MD_SPAN_LATEXMATH : MD_SPAN_LATEXMATH_DISPLAY, NULL); + text_type = MD_TEXT_NORMAL; + } + break; + + case '[': /* Link, wiki link, image. */ case '!': case ']': { const MD_MARK* opener = (mark->ch != ']' ? mark : &ctx->marks[mark->prev]); - const MD_MARK* dest_mark = opener+1; - const MD_MARK* title_mark = opener+2; + const MD_MARK* closer = &ctx->marks[opener->next]; + const MD_MARK* dest_mark; + const MD_MARK* title_mark; + + if ((opener->ch == '[' && closer->ch == ']') && + opener->end - opener->beg >= 2 && + closer->end - closer->beg >= 2) + { + int has_label = (opener->end - opener->beg > 2); + SZ target_sz; + + if(has_label) + target_sz = opener->end - (opener->beg+2); + else + target_sz = closer->beg - opener->end; + + MD_CHECK(md_enter_leave_span_wikilink(ctx, (mark->ch != ']'), + has_label ? STR(opener->beg+2) : STR(opener->end), + target_sz)); + + break; + } + dest_mark = opener+1; MD_ASSERT(dest_mark->ch == 'D'); - MD_ASSERT(title_mark->ch == 'D'); + title_mark = opener+2; + if (title_mark->ch != 'D') break; MD_CHECK(md_enter_leave_span_a(ctx, (mark->ch != ']'), (opener->ch == '!' ? MD_SPAN_IMG : MD_SPAN_A), STR(dest_mark->beg), dest_mark->end - dest_mark->beg, FALSE, - md_mark_get_ptr(ctx, title_mark - ctx->marks), title_mark->prev)); + md_mark_get_ptr(ctx, (int)(title_mark - ctx->marks)), + title_mark->prev)); + + /* link/image closer may span multiple lines. */ + if(mark->ch == ']') { + while(mark->end > line->end) + line++; + } + break; } @@ -3961,16 +4335,26 @@ md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines) break; } /* Pass through, if auto-link. */ + MD_FALLTHROUGH(); case '@': /* Permissive e-mail autolink. */ case ':': /* Permissive URL autolink. */ case '.': /* Permissive WWW autolink. */ { - const MD_MARK* opener = ((mark->flags & MD_MARK_OPENER) ? mark : &ctx->marks[mark->prev]); - const MD_MARK* closer = &ctx->marks[opener->next]; + MD_MARK* opener = ((mark->flags & MD_MARK_OPENER) ? mark : &ctx->marks[mark->prev]); + MD_MARK* closer = &ctx->marks[opener->next]; const CHAR* dest = STR(opener->end); SZ dest_size = closer->beg - opener->end; + /* For permissive auto-links we do not know closer mark + * position at the time of md_collect_marks(), therefore + * it can be out-of-order in ctx->marks[]. + * + * With this flag, we make sure that we output the closer + * only if we processed the opener. */ + if(mark->flags & MD_MARK_OPENER) + closer->flags |= MD_MARK_VALIDPERMISSIVEAUTOLINK; + if(opener->ch == '@' || opener->ch == '.') { dest_size += 7; MD_TEMP_BUFFER(dest_size * sizeof(CHAR)); @@ -3981,8 +4365,9 @@ md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines) dest = ctx->buffer; } - MD_CHECK(md_enter_leave_span_a(ctx, (mark->flags & MD_MARK_OPENER), - MD_SPAN_A, dest, dest_size, TRUE, NULL, 0)); + if(closer->flags & MD_MARK_VALIDPERMISSIVEAUTOLINK) + MD_CHECK(md_enter_leave_span_a(ctx, (mark->flags & MD_MARK_OPENER), + MD_SPAN_A, dest, dest_size, TRUE, NULL, 0)); break; } @@ -3993,6 +4378,9 @@ md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines) case '\0': MD_TEXT(MD_TEXT_NULLCHAR, _T(""), 1); break; + + case 127: + goto abort; } off = mark->end; @@ -4010,15 +4398,24 @@ md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines) if(off >= end) break; - if(text_type == MD_TEXT_CODE) { - /* Inside code spans, new lines are transformed into single - * spaces. */ + if(text_type == MD_TEXT_CODE || text_type == MD_TEXT_LATEXMATH) { + OFF tmp; + MD_ASSERT(prev_mark != NULL); - MD_ASSERT(prev_mark->ch == '`' && (prev_mark->flags & MD_MARK_OPENER)); - MD_ASSERT(mark->ch == '`' && (mark->flags & MD_MARK_CLOSER)); + MD_ASSERT(ISANYOF2_(prev_mark->ch, '`', '$') && (prev_mark->flags & MD_MARK_OPENER)); + MD_ASSERT(ISANYOF2_(mark->ch, '`', '$') && (mark->flags & MD_MARK_CLOSER)); + + /* Inside a code span, trailing line whitespace has to be + * outputted. */ + tmp = off; + while(off < ctx->size && ISBLANK(off)) + off++; + if(off > tmp) + MD_TEXT(text_type, STR(tmp), off-tmp); + /* and new lines are transformed into single spaces. */ if(prev_mark->end < off && off < mark->beg) - MD_TEXT(MD_TEXT_CODE, _T(" "), 1); + MD_TEXT(text_type, _T(" "), 1); } else if(text_type == MD_TEXT_HTML) { /* Inside raw HTML, we output the new line verbatim, including * any trailing spaces. */ @@ -4043,7 +4440,7 @@ md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines) MD_TEXT(break_type, _T("\n"), 1); } - /* Switch to the following line. */ + /* Move to the next line. */ line++; off = line->beg; @@ -4114,49 +4511,48 @@ abort: static int md_process_table_row(MD_CTX* ctx, MD_BLOCKTYPE cell_type, OFF beg, OFF end, - const MD_ALIGN* align, int n_align) + const MD_ALIGN* align, int col_count) { - MD_LINE line = { beg, end }; + MD_LINE line; OFF* pipe_offs = NULL; - int i, j, n; + int i, j, k, n; int ret = 0; + line.beg = beg; + line.end = end; + /* Break the line into table cells by identifying pipe characters who * form the cell boundary. */ MD_CHECK(md_analyze_inlines(ctx, &line, 1, TRUE)); /* We have to remember the cell boundaries in local buffer because * ctx->marks[] shall be reused during cell contents processing. */ - n = ctx->n_table_cell_boundaries; + n = ctx->n_table_cell_boundaries + 2; pipe_offs = (OFF*) malloc(n * sizeof(OFF)); if(pipe_offs == NULL) { MD_LOG("malloc() failed."); ret = -1; goto abort; } - for(i = TABLECELLBOUNDARIES.head, j = 0; i >= 0; i = ctx->marks[i].next) { + j = 0; + pipe_offs[j++] = beg; + for(i = TABLECELLBOUNDARIES.head; i >= 0; i = ctx->marks[i].next) { MD_MARK* mark = &ctx->marks[i]; - pipe_offs[j++] = mark->beg; + pipe_offs[j++] = mark->end; } + pipe_offs[j++] = end+1; /* Process cells. */ MD_ENTER_BLOCK(MD_BLOCK_TR, NULL); - j = 0; - if(beg < pipe_offs[0]) { - MD_CHECK(md_process_table_cell(ctx, cell_type, - (j < n_align ? align[j++] : MD_ALIGN_DEFAULT), - beg, pipe_offs[0])); - } - for(i = 0; i < n-1; i++) { - MD_CHECK(md_process_table_cell(ctx, cell_type, - (j < n_align ? align[j++] : MD_ALIGN_DEFAULT), - pipe_offs[i]+1, pipe_offs[i+1])); - } - if(pipe_offs[n-1] < end-1) { - MD_CHECK(md_process_table_cell(ctx, cell_type, - (j < n_align ? align[j++] : MD_ALIGN_DEFAULT), - pipe_offs[n-1]+1, end)); - } + k = 0; + for(i = 0; i < j-1 && k < col_count; i++) { + if(pipe_offs[i] < pipe_offs[i+1]-1) + MD_CHECK(md_process_table_cell(ctx, cell_type, align[k++], pipe_offs[i], pipe_offs[i+1]-1)); + } + /* Make sure we call enough table cells even if the current table contains + * too few of them. */ + while(k < col_count) + MD_CHECK(md_process_table_cell(ctx, cell_type, align[k++], 0, 0)); MD_LEAVE_BLOCK(MD_BLOCK_TR, NULL); abort: @@ -4178,8 +4574,8 @@ md_process_table_block_contents(MD_CTX* ctx, int col_count, const MD_LINE* lines int i; int ret = 0; - /* At least the line with column names and the table underline have to - * be present. */ + /* At least two lines have to be present: The column headers and the line + * with the underlines. */ MD_ASSERT(n_lines >= 2); align = malloc(col_count * sizeof(MD_ALIGN)); @@ -4196,47 +4592,20 @@ md_process_table_block_contents(MD_CTX* ctx, int col_count, const MD_LINE* lines lines[0].beg, lines[0].end, align, col_count)); MD_LEAVE_BLOCK(MD_BLOCK_THEAD, NULL); - MD_ENTER_BLOCK(MD_BLOCK_TBODY, NULL); - for(i = 2; i < n_lines; i++) { - MD_CHECK(md_process_table_row(ctx, MD_BLOCK_TD, - lines[i].beg, lines[i].end, align, col_count)); + if(n_lines > 2) { + MD_ENTER_BLOCK(MD_BLOCK_TBODY, NULL); + for(i = 2; i < n_lines; i++) { + MD_CHECK(md_process_table_row(ctx, MD_BLOCK_TD, + lines[i].beg, lines[i].end, align, col_count)); + } + MD_LEAVE_BLOCK(MD_BLOCK_TBODY, NULL); } - MD_LEAVE_BLOCK(MD_BLOCK_TBODY, NULL); abort: free(align); return ret; } -static int -md_is_table_row(MD_CTX* ctx, OFF beg, OFF* p_end) -{ - MD_LINE line = { beg, beg }; - int i; - int ret = FALSE; - - /* Find end of line. */ - while(line.end < ctx->size && !ISNEWLINE(line.end)) - line.end++; - - MD_CHECK(md_analyze_inlines(ctx, &line, 1, TRUE)); - - if(TABLECELLBOUNDARIES.head >= 0) { - if(p_end != NULL) - *p_end = line.end; - ret = TRUE; - } - -abort: - /* Free any temporary memory blocks stored within some dummy marks. */ - for(i = PTR_CHAIN.head; i >= 0; i = ctx->marks[i].next) - free(md_mark_get_ptr(ctx, i)); - PTR_CHAIN.head = -1; - PTR_CHAIN.tail = -1; - - return ret; -} - /************************** *** Processing Block *** @@ -4246,6 +4615,7 @@ abort: #define MD_BLOCK_CONTAINER_CLOSER 0x02 #define MD_BLOCK_CONTAINER (MD_BLOCK_CONTAINER_OPENER | MD_BLOCK_CONTAINER_CLOSER) #define MD_BLOCK_LOOSE_LIST 0x04 +#define MD_BLOCK_SETEXT_HEADER 0x08 struct MD_BLOCK_tag { MD_BLOCKTYPE type : 8; @@ -4253,11 +4623,13 @@ struct MD_BLOCK_tag { /* MD_BLOCK_H: Header level (1 - 6) * MD_BLOCK_CODE: Non-zero if fenced, zero if indented. - * MD_BLOCK_TABLE: Column count (as determined by the table underline) + * MD_BLOCK_LI: Task mark character (0 if not task list item, 'x', 'X' or ' '). + * MD_BLOCK_TABLE: Column count (as determined by the table underline). */ unsigned data : 16; /* Leaf blocks: Count of lines (MD_LINE or MD_VERBATIMLINE) on the block. + * MD_BLOCK_LI: Task mark offset in the input doc. * MD_BLOCK_OL: Start item number. */ unsigned n_lines; @@ -4266,10 +4638,12 @@ struct MD_BLOCK_tag { struct MD_CONTAINER_tag { CHAR ch; unsigned is_loose : 8; + unsigned is_task : 8; unsigned start; unsigned mark_indent; unsigned contents_indent; OFF block_byte_off; + OFF task_mark_off; }; @@ -4308,9 +4682,9 @@ md_process_verbatim_block_contents(MD_CTX* ctx, MD_TEXTTYPE text_type, const MD_ MD_ASSERT(indent >= 0); /* Output code indentation. */ - while(indent > SIZEOF_ARRAY(indent_chunk_str)) { + while(indent > (int) indent_chunk_size) { MD_TEXT(text_type, indent_chunk_str, indent_chunk_size); - indent -= SIZEOF_ARRAY(indent_chunk_str); + indent -= indent_chunk_size; } if(indent > 0) MD_TEXT(text_type, indent_chunk_str, indent); @@ -4382,6 +4756,8 @@ md_setup_fenced_code_detail(MD_CTX* ctx, const MD_BLOCK* block, MD_BLOCK_CODE_DE lang_end++; MD_CHECK(md_build_attribute(ctx, STR(beg), lang_end - beg, 0, &det->lang, lang_build)); + det->fence_char = fence_ch; + abort: return ret; } @@ -4392,6 +4768,7 @@ md_process_leaf_block(MD_CTX* ctx, const MD_BLOCK* block) union { MD_BLOCK_H_DETAIL header; MD_BLOCK_CODE_DETAIL code; + MD_BLOCK_TABLE_DETAIL table; } det; MD_ATTRIBUTE_BUILD info_build; MD_ATTRIBUTE_BUILD lang_build; @@ -4420,6 +4797,12 @@ md_process_leaf_block(MD_CTX* ctx, const MD_BLOCK* block) } break; + case MD_BLOCK_TABLE: + det.table.col_count = block->data; + det.table.head_row_count = 1; + det.table.body_row_count = block->n_lines - 2; + break; + default: /* Noop. */ break; @@ -4483,6 +4866,7 @@ md_process_all_blocks(MD_CTX* ctx) union { MD_BLOCK_UL_DETAIL ul; MD_BLOCK_OL_DETAIL ol; + MD_BLOCK_LI_DETAIL li; } det; switch(block->type) { @@ -4497,6 +4881,12 @@ md_process_all_blocks(MD_CTX* ctx) det.ol.mark_delimiter = (CHAR) block->data; break; + case MD_BLOCK_LI: + det.li.is_task = (block->data != 0); + det.li.task_mark = (CHAR) block->data; + det.li.task_mark_offset = (OFF) block->n_lines; + break; + default: /* noop */ break; @@ -4555,7 +4945,9 @@ md_push_block_bytes(MD_CTX* ctx, int n_bytes) if(ctx->n_block_bytes + n_bytes > ctx->alloc_block_bytes) { void* new_block_bytes; - ctx->alloc_block_bytes = (ctx->alloc_block_bytes > 0 ? ctx->alloc_block_bytes * 2 : 512); + ctx->alloc_block_bytes = (ctx->alloc_block_bytes > 0 + ? ctx->alloc_block_bytes + ctx->alloc_block_bytes / 2 + : 512); new_block_bytes = realloc(ctx->block_bytes, ctx->alloc_block_bytes); if(new_block_bytes == NULL) { MD_LOG("realloc() failed."); @@ -4564,7 +4956,7 @@ md_push_block_bytes(MD_CTX* ctx, int n_bytes) /* Fix the ->current_block after the reallocation. */ if(ctx->current_block != NULL) { - OFF off_current_block = (char*) ctx->current_block - (char*) ctx->block_bytes; + OFF off_current_block = (OFF) ((char*) ctx->current_block - (char*) ctx->block_bytes); ctx->current_block = (MD_BLOCK*) ((char*) new_block_bytes + off_current_block); } @@ -4665,6 +5057,7 @@ md_consume_link_reference_definitions(MD_CTX* ctx) /* Remove complete block. */ ctx->n_block_bytes -= n * sizeof(MD_LINE); ctx->n_block_bytes -= sizeof(MD_BLOCK); + ctx->current_block = NULL; } else { /* Remove just some initial lines from the block. */ memmove(lines, lines + n, (n_lines - n) * sizeof(MD_LINE)); @@ -4687,10 +5080,30 @@ md_end_current_block(MD_CTX* ctx) /* Check whether there is a reference definition. (We do this here instead * of in md_analyze_line() because reference definition can take multiple * lines.) */ - if(ctx->current_block->type == MD_BLOCK_P) { + if(ctx->current_block->type == MD_BLOCK_P || + (ctx->current_block->type == MD_BLOCK_H && (ctx->current_block->flags & MD_BLOCK_SETEXT_HEADER))) + { MD_LINE* lines = (MD_LINE*) (ctx->current_block + 1); - if(CH(lines[0].beg) == _T('[')) + if(CH(lines[0].beg) == _T('[')) { MD_CHECK(md_consume_link_reference_definitions(ctx)); + if(ctx->current_block == NULL) + return ret; + } + } + + if(ctx->current_block->type == MD_BLOCK_H && (ctx->current_block->flags & MD_BLOCK_SETEXT_HEADER)) { + int n_lines = ctx->current_block->n_lines; + + if(n_lines > 1) { + /* Get rid of the underline. */ + ctx->current_block->n_lines--; + ctx->n_block_bytes -= sizeof(MD_LINE); + } else { + /* Only the underline has left after eating the ref. defs. + * Keep the line as beginning of a new ordinary paragraph. */ + ctx->current_block->type = MD_BLOCK_P; + return 0; + } } /* Mark we are not building any block anymore. */ @@ -4759,7 +5172,7 @@ abort: ***********************/ static int -md_is_hr_line(MD_CTX* ctx, OFF beg, OFF* p_end) +md_is_hr_line(MD_CTX* ctx, OFF beg, OFF* p_end, OFF* p_killer) { OFF off = beg + 1; int n = 1; @@ -4770,12 +5183,16 @@ md_is_hr_line(MD_CTX* ctx, OFF beg, OFF* p_end) off++; } - if(n < 3) + if(n < 3) { + *p_killer = off; return FALSE; + } /* Nothing else can be present on the line. */ - if(off < ctx->size && !ISNEWLINE(off)) + if(off < ctx->size && !ISNEWLINE(off)) { + *p_killer = off; return FALSE; + } *p_end = off; return TRUE; @@ -4795,13 +5212,14 @@ md_is_atxheader_line(MD_CTX* ctx, OFF beg, OFF* p_beg, OFF* p_end, unsigned* p_l return FALSE; *p_level = n; - if(!(ctx->r.flags & MD_FLAG_PERMISSIVEATXHEADERS) && off < ctx->size && + if(!(ctx->parser.flags & MD_FLAG_PERMISSIVEATXHEADERS) && off < ctx->size && CH(off) != _T(' ') && CH(off) != _T('\t') && !ISNEWLINE(off)) return FALSE; while(off < ctx->size && CH(off) == _T(' ')) off++; *p_beg = off; + *p_end = off; return TRUE; } @@ -4813,9 +5231,6 @@ md_is_setext_underline(MD_CTX* ctx, OFF beg, OFF* p_end, unsigned* p_level) while(off < ctx->size && CH(off) == CH(beg)) off++; - while(off < ctx->size && CH(off) == _T(' ')) - off++; - /* Optionally, space(s) can follow. */ while(off < ctx->size && CH(off) == _T(' ')) off++; @@ -4844,19 +5259,17 @@ md_is_table_underline(MD_CTX* ctx, OFF beg, OFF* p_end, unsigned* p_col_count) } while(1) { - OFF cell_beg; int delimited = FALSE; /* Cell underline ("-----", ":----", "----:" or ":----:") */ - cell_beg = off; if(off < ctx->size && CH(off) == _T(':')) off++; + if(off >= ctx->size || CH(off) != _T('-')) + return FALSE; while(off < ctx->size && CH(off) == _T('-')) off++; if(off < ctx->size && CH(off) == _T(':')) off++; - if(off - cell_beg < 3) - return FALSE; col_count++; @@ -4905,11 +5318,13 @@ md_is_opening_code_fence(MD_CTX* ctx, OFF beg, OFF* p_end) while(off < ctx->size && CH(off) == _T(' ')) off++; - /* Optionally, an info string can follow. It must not contain '`'. */ - while(off < ctx->size && CH(off) != _T('`') && !ISNEWLINE(off)) + /* Optionally, an info string can follow. */ + while(off < ctx->size && !ISNEWLINE(off)) { + /* Backtick-based fence must not contain '`' in the info string. */ + if(CH(beg) == _T('`') && CH(off) == _T('`')) + return FALSE; off++; - if(off < ctx->size && !ISNEWLINE(off)) - return FALSE; + } *p_end = off; return TRUE; @@ -4962,9 +5377,9 @@ md_is_html_block_start_condition(MD_CTX* ctx, OFF beg) #ifdef X #undef X #endif -#define X(name) { _T(name), sizeof(name)-1 } +#define X(name) { _T(name), (sizeof(name)-1) / sizeof(CHAR) } #define Xend { NULL, 0 } - static const TAG t1[] = { X("script"), X("pre"), X("style"), Xend }; + static const TAG t1[] = { X("pre"), X("script"), X("style"), X("textarea"), Xend }; static const TAG a6[] = { X("address"), X("article"), X("aside"), Xend }; static const TAG b6[] = { X("base"), X("basefont"), X("blockquote"), X("body"), Xend }; @@ -4976,7 +5391,7 @@ md_is_html_block_start_condition(MD_CTX* ctx, OFF beg) static const TAG h6[] = { X("h1"), X("head"), X("header"), X("hr"), X("html"), Xend }; static const TAG i6[] = { X("iframe"), Xend }; static const TAG l6[] = { X("legend"), X("li"), X("link"), Xend }; - static const TAG m6[] = { X("main"), X("menu"), X("menuitem"), X("meta"), Xend }; + static const TAG m6[] = { X("main"), X("menu"), X("menuitem"), Xend }; static const TAG n6[] = { X("nav"), X("noframes"), Xend }; static const TAG o6[] = { X("ol"), X("optgroup"), X("option"), Xend }; static const TAG p6[] = { X("p"), X("param"), Xend }; @@ -4996,7 +5411,7 @@ md_is_html_block_start_condition(MD_CTX* ctx, OFF beg) /* Check for type 1: <script, <pre, or <style */ for(i = 0; t1[i].name != NULL; i++) { - if(off + t1[i].len < ctx->size) { + if(off + t1[i].len <= ctx->size) { if(md_ascii_case_eq(STR(off), t1[i].name, t1[i].len)) return 1; } @@ -5013,12 +5428,12 @@ md_is_html_block_start_condition(MD_CTX* ctx, OFF beg) /* Check for type 4 or 5: <! */ if(off < ctx->size && CH(off) == _T('!')) { /* Check for type 4: <! followed by uppercase letter. */ - if(off + 1 < ctx->size && ISUPPER(off+1)) + if(off + 1 < ctx->size && ISASCII(off+1)) return 4; /* Check for type 5: <![CDATA[ */ if(off + 8 < ctx->size) { - if(md_ascii_eq(STR(off), _T("![CDATA["), 8 * sizeof(CHAR))) + if(md_ascii_eq(STR(off), _T("![CDATA["), 8)) return 5; } } @@ -5100,20 +5515,16 @@ md_is_html_block_end_condition(MD_CTX* ctx, OFF beg, OFF* p_end) while(off < ctx->size && !ISNEWLINE(off)) { if(CH(off) == _T('<')) { - if(md_ascii_case_eq(STR(off), _T("</script>"), 9)) { - *p_end = off + 9; - return TRUE; - } - - if(md_ascii_case_eq(STR(off), _T("</style>"), 8)) { - *p_end = off + 8; - return TRUE; - } - - if(md_ascii_case_eq(STR(off), _T("</pre>"), 6)) { - *p_end = off + 6; - return TRUE; + #define FIND_TAG_END(string, length) \ + if(off + length <= ctx->size && \ + md_ascii_case_eq(STR(off), _T(string), length)) { \ + *p_end = off + length; \ + return TRUE; \ } + FIND_TAG_END("</script>", 9) + FIND_TAG_END("</style>", 8) + FIND_TAG_END("</pre>", 6) + #undef FIND_TAG_END } off++; @@ -5137,11 +5548,12 @@ md_is_html_block_end_condition(MD_CTX* ctx, OFF beg, OFF* p_end) case 6: /* Pass through */ case 7: *p_end = beg; - return (ISNEWLINE(beg) ? ctx->html_block_type : FALSE); + return (beg >= ctx->size || ISNEWLINE(beg) ? ctx->html_block_type : FALSE); default: MD_UNREACHABLE(); } + return FALSE; } @@ -5166,7 +5578,9 @@ md_push_container(MD_CTX* ctx, const MD_CONTAINER* container) if(ctx->n_containers >= ctx->alloc_containers) { MD_CONTAINER* new_containers; - ctx->alloc_containers = (ctx->alloc_containers > 0 ? ctx->alloc_containers * 2 : 16); + ctx->alloc_containers = (ctx->alloc_containers > 0 + ? ctx->alloc_containers + ctx->alloc_containers / 2 + : 16); new_containers = realloc(ctx->containers, ctx->alloc_containers * sizeof(MD_CONTAINER)); if(new_containers == NULL) { MD_LOG("realloc() failed."); @@ -5181,7 +5595,7 @@ md_push_container(MD_CTX* ctx, const MD_CONTAINER* container) } static int -md_enter_child_containers(MD_CTX* ctx, int n_children, unsigned data) +md_enter_child_containers(MD_CTX* ctx, int n_children) { int i; int ret = 0; @@ -5194,7 +5608,7 @@ md_enter_child_containers(MD_CTX* ctx, int n_children, unsigned data) case _T(')'): case _T('.'): is_ordered_list = TRUE; - /* Pass through */ + MD_FALLTHROUGH(); case _T('-'): case _T('+'): @@ -5206,12 +5620,15 @@ md_enter_child_containers(MD_CTX* ctx, int n_children, unsigned data) MD_CHECK(md_push_container_bytes(ctx, (is_ordered_list ? MD_BLOCK_OL : MD_BLOCK_UL), - c->start, data, MD_BLOCK_CONTAINER_OPENER)); - MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI, 0, data, MD_BLOCK_CONTAINER_OPENER)); + c->start, c->ch, MD_BLOCK_CONTAINER_OPENER)); + MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI, + c->task_mark_off, + (c->is_task ? CH(c->task_mark_off) : 0), + MD_BLOCK_CONTAINER_OPENER)); break; case _T('>'): - MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_QUOTE, 0, data, MD_BLOCK_CONTAINER_OPENER)); + MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_QUOTE, 0, 0, MD_BLOCK_CONTAINER_OPENER)); break; default: @@ -5237,13 +5654,14 @@ md_leave_child_containers(MD_CTX* ctx, int n_keep) case _T(')'): case _T('.'): is_ordered_list = TRUE; - /* Pass through */ + MD_FALLTHROUGH(); case _T('-'): case _T('+'): case _T('*'): - MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI, 0, - 0, MD_BLOCK_CONTAINER_CLOSER)); + MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI, + c->task_mark_off, (c->is_task ? CH(c->task_mark_off) : 0), + MD_BLOCK_CONTAINER_CLOSER)); MD_CHECK(md_push_container_bytes(ctx, (is_ordered_list ? MD_BLOCK_OL : MD_BLOCK_UL), 0, c->ch, MD_BLOCK_CONTAINER_CLOSER)); @@ -5272,11 +5690,15 @@ md_is_container_mark(MD_CTX* ctx, unsigned indent, OFF beg, OFF* p_end, MD_CONTA OFF off = beg; OFF max_end; + if(off >= ctx->size || indent >= ctx->code_indent_offset) + return FALSE; + /* Check for block quote mark. */ - if(off < ctx->size && CH(off) == _T('>')) { + if(CH(off) == _T('>')) { off++; p_container->ch = _T('>'); p_container->is_loose = FALSE; + p_container->is_task = FALSE; p_container->mark_indent = indent; p_container->contents_indent = indent + 1; *p_end = off; @@ -5284,9 +5706,10 @@ md_is_container_mark(MD_CTX* ctx, unsigned indent, OFF beg, OFF* p_end, MD_CONTA } /* Check for list item bullet mark. */ - if(off+1 < ctx->size && ISANYOF(off, _T("-+*")) && (ISBLANK(off+1) || ISNEWLINE(off+1))) { + if(ISANYOF(off, _T("-+*")) && (off+1 >= ctx->size || ISBLANK(off+1) || ISNEWLINE(off+1))) { p_container->ch = CH(off); p_container->is_loose = FALSE; + p_container->is_task = FALSE; p_container->mark_indent = indent; p_container->contents_indent = indent + 1; *p_end = off+1; @@ -5302,9 +5725,14 @@ md_is_container_mark(MD_CTX* ctx, unsigned indent, OFF beg, OFF* p_end, MD_CONTA p_container->start = p_container->start * 10 + CH(off) - _T('0'); off++; } - if(off+1 < ctx->size && (CH(off) == _T('.') || CH(off) == _T(')')) && (ISBLANK(off+1) || ISNEWLINE(off+1))) { + if(off > beg && + off < ctx->size && + (CH(off) == _T('.') || CH(off) == _T(')')) && + (off+1 >= ctx->size || ISBLANK(off+1) || ISNEWLINE(off+1))) + { p_container->ch = CH(off); p_container->is_loose = FALSE; + p_container->is_task = FALSE; p_container->mark_indent = indent; p_container->contents_indent = indent + off - beg + 1; *p_end = off+1; @@ -5332,7 +5760,7 @@ md_line_indentation(MD_CTX* ctx, unsigned total_indent, OFF beg, OFF* p_end) return indent - total_indent; } -static const MD_LINE_ANALYSIS md_dummy_blank_line = { MD_LINE_BLANK, 0 }; +static const MD_LINE_ANALYSIS md_dummy_blank_line = { MD_LINE_BLANK, 0, 0, 0, 0 }; /* Analyze type of the line and find some its properties. This serves as a * main input for determining type and boundaries of a block. */ @@ -5344,9 +5772,10 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end, int n_parents = 0; int n_brothers = 0; int n_children = 0; - MD_CONTAINER container; + MD_CONTAINER container = { 0 }; int prev_line_has_list_loosening_effect = ctx->last_line_has_list_loosening_effect; OFF off = beg; + OFF hr_killer = 0; int ret = 0; line->indent = md_line_indentation(ctx, total_indent, off, &off); @@ -5372,6 +5801,7 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end, line->indent--; line->beg = off; + } else if(c->ch != _T('>') && line->indent >= c->contents_indent) { /* List. */ line->indent -= c->contents_indent; @@ -5382,331 +5812,377 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end, n_parents++; } -redo: - /* Check whether we are fenced code continuation. */ - if(pivot_line->type == MD_LINE_FENCEDCODE) { - line->beg = off; + if(off >= ctx->size || ISNEWLINE(off)) { + /* Blank line does not need any real indentation to be nested inside + * a list. */ + if(n_brothers + n_children == 0) { + while(n_parents < ctx->n_containers && ctx->containers[n_parents].ch != _T('>')) + n_parents++; + } + } - /* We are another MD_LINE_FENCEDCODE unless we are closing fence - * which we transform into MD_LINE_BLANK. */ - if(line->indent < ctx->code_indent_offset) { - if(md_is_closing_code_fence(ctx, CH(pivot_line->beg), off, &off)) { - line->type = MD_LINE_BLANK; - ctx->last_line_has_list_loosening_effect = FALSE; - goto done; + while(TRUE) { + /* Check whether we are fenced code continuation. */ + if(pivot_line->type == MD_LINE_FENCEDCODE) { + line->beg = off; + + /* We are another MD_LINE_FENCEDCODE unless we are closing fence + * which we transform into MD_LINE_BLANK. */ + if(line->indent < ctx->code_indent_offset) { + if(md_is_closing_code_fence(ctx, CH(pivot_line->beg), off, &off)) { + line->type = MD_LINE_BLANK; + ctx->last_line_has_list_loosening_effect = FALSE; + break; + } } - } - if(off >= ctx->size || ISNEWLINE(off)) { - /* Blank line does not need any real indentation to be nested inside - * a list. */ - if(n_brothers + n_children == 0) { - while(n_parents < ctx->n_containers && ctx->containers[n_parents].ch != _T('>')) - n_parents++; + /* Change indentation accordingly to the initial code fence. */ + if(n_parents == ctx->n_containers) { + if(line->indent > pivot_line->indent) + line->indent -= pivot_line->indent; + else + line->indent = 0; + + line->type = MD_LINE_FENCEDCODE; + break; } } - /* Change indentation accordingly to the initial code fence. */ - if(n_parents == ctx->n_containers) { - if(line->indent > pivot_line->indent) - line->indent -= pivot_line->indent; - else - line->indent = 0; + /* Check whether we are HTML block continuation. */ + if(pivot_line->type == MD_LINE_HTML && ctx->html_block_type > 0) { + if(n_parents < ctx->n_containers) { + /* HTML block is implicitly ended if the enclosing container + * block ends. */ + ctx->html_block_type = 0; + } else { + int html_block_type; - line->type = MD_LINE_FENCEDCODE; - goto done; - } - } + html_block_type = md_is_html_block_end_condition(ctx, off, &off); + if(html_block_type > 0) { + MD_ASSERT(html_block_type == ctx->html_block_type); - /* Check whether we are HTML block continuation. */ - if(pivot_line->type == MD_LINE_HTML && ctx->html_block_type > 0) { - int html_block_type; + /* Make sure this is the last line of the block. */ + ctx->html_block_type = 0; - html_block_type = md_is_html_block_end_condition(ctx, off, &off); - if(html_block_type > 0) { - MD_ASSERT(html_block_type == ctx->html_block_type); + /* Some end conditions serve as blank lines at the same time. */ + if(html_block_type == 6 || html_block_type == 7) { + line->type = MD_LINE_BLANK; + line->indent = 0; + break; + } + } - /* Make sure this is the last line of the block. */ - ctx->html_block_type = 0; + line->type = MD_LINE_HTML; + n_parents = ctx->n_containers; + break; + } + } - /* Some end conditions serve as blank lines at the same time. */ - if(html_block_type == 6 || html_block_type == 7) { + /* Check for blank line. */ + if(off >= ctx->size || ISNEWLINE(off)) { + if(pivot_line->type == MD_LINE_INDENTEDCODE && n_parents == ctx->n_containers) { + line->type = MD_LINE_INDENTEDCODE; + if(line->indent > ctx->code_indent_offset) + line->indent -= ctx->code_indent_offset; + else + line->indent = 0; + ctx->last_line_has_list_loosening_effect = FALSE; + } else { line->type = MD_LINE_BLANK; - line->indent = 0; - goto done; + ctx->last_line_has_list_loosening_effect = (n_parents > 0 && + n_brothers + n_children == 0 && + ctx->containers[n_parents-1].ch != _T('>')); + + #if 1 + /* See https://github.com/mity/md4c/issues/6 + * + * This ugly checking tests we are in (yet empty) list item but + * not its very first line (i.e. not the line with the list + * item mark). + * + * If we are such a blank line, then any following non-blank + * line which would be part of the list item actually has to + * end the list because according to the specification, "a list + * item can begin with at most one blank line." + */ + if(n_parents > 0 && ctx->containers[n_parents-1].ch != _T('>') && + n_brothers + n_children == 0 && ctx->current_block == NULL && + ctx->n_block_bytes > (int) sizeof(MD_BLOCK)) + { + MD_BLOCK* top_block = (MD_BLOCK*) ((char*)ctx->block_bytes + ctx->n_block_bytes - sizeof(MD_BLOCK)); + if(top_block->type == MD_BLOCK_LI) + ctx->last_list_item_starts_with_two_blank_lines = TRUE; + } + #endif } - } + break; + } else { + #if 1 + /* This is the 2nd half of the hack. If the flag is set (i.e. there + * was a 2nd blank line at the beginning of the list item) and if + * we would otherwise still belong to the list item, we enforce + * the end of the list. */ + ctx->last_line_has_list_loosening_effect = FALSE; + if(ctx->last_list_item_starts_with_two_blank_lines) { + if(n_parents > 0 && ctx->containers[n_parents-1].ch != _T('>') && + n_brothers + n_children == 0 && ctx->current_block == NULL && + ctx->n_block_bytes > (int) sizeof(MD_BLOCK)) + { + MD_BLOCK* top_block = (MD_BLOCK*) ((char*)ctx->block_bytes + ctx->n_block_bytes - sizeof(MD_BLOCK)); + if(top_block->type == MD_BLOCK_LI) + n_parents--; + } - if(n_parents == ctx->n_containers) { - line->type = MD_LINE_HTML; - goto done; + ctx->last_list_item_starts_with_two_blank_lines = FALSE; + } + #endif } - } - /* Check for blank line. */ - if(off >= ctx->size || ISNEWLINE(off)) { - /* Blank line does not need any real indentation to be nested inside - * a list. */ - if(n_brothers + n_children == 0) { - while(n_parents < ctx->n_containers && ctx->containers[n_parents].ch != _T('>')) - n_parents++; + /* Check whether we are Setext underline. */ + if(line->indent < ctx->code_indent_offset && pivot_line->type == MD_LINE_TEXT + && off < ctx->size && ISANYOF2(off, _T('='), _T('-')) + && (n_parents == ctx->n_containers)) + { + unsigned level; + + if(md_is_setext_underline(ctx, off, &off, &level)) { + line->type = MD_LINE_SETEXTUNDERLINE; + line->data = level; + break; + } } - if(pivot_line->type == MD_LINE_INDENTEDCODE && n_parents == ctx->n_containers) { - line->type = MD_LINE_INDENTEDCODE; - if(line->indent > ctx->code_indent_offset) - line->indent -= ctx->code_indent_offset; - else - line->indent = 0; - ctx->last_line_has_list_loosening_effect = FALSE; - } else { - line->type = MD_LINE_BLANK; - ctx->last_line_has_list_loosening_effect = (n_parents > 0 && - n_brothers + n_children == 0 && - ctx->containers[n_parents-1].ch != _T('>')); - -#if 1 - /* See https://github.com/mity/md4c/issues/6 - * - * This ugly checking tests we are in (yet empty) list item but not - * its very first line (with the list item mark). - * - * If we are such blank line, then any following non-blank line - * which would be part of this list item actually ends the list - * because "a list item can begin with at most one blank line." - */ - if(n_parents > 0 && ctx->containers[n_parents-1].ch != _T('>') && - n_brothers + n_children == 0 && ctx->current_block == NULL && - ctx->n_block_bytes > sizeof(MD_BLOCK)) - { - MD_BLOCK* top_block = (MD_BLOCK*) ((char*)ctx->block_bytes + ctx->n_block_bytes - sizeof(MD_BLOCK)); - if(top_block->type == MD_BLOCK_LI) - ctx->last_list_item_starts_with_two_blank_lines = TRUE; + /* Check for thematic break line. */ + if(line->indent < ctx->code_indent_offset + && off < ctx->size && off >= hr_killer + && ISANYOF(off, _T("-_*"))) + { + if(md_is_hr_line(ctx, off, &off, &hr_killer)) { + line->type = MD_LINE_HR; + break; } -#endif } - goto done_on_eol; - } else { -#if 1 - /* This is 2nd half of the hack. If the flag is set (that is there - * were 2nd blank line at the start of the list item) and we would also - * belonging to such list item, then interrupt the list. */ - ctx->last_line_has_list_loosening_effect = FALSE; - if(ctx->last_list_item_starts_with_two_blank_lines) { - if(n_parents > 0 && ctx->containers[n_parents-1].ch != _T('>') && - n_brothers + n_children == 0 && ctx->current_block == NULL && - ctx->n_block_bytes > sizeof(MD_BLOCK)) + + /* Check for "brother" container. I.e. whether we are another list item + * in already started list. */ + if(n_parents < ctx->n_containers && n_brothers + n_children == 0) { + OFF tmp; + + if(md_is_container_mark(ctx, line->indent, off, &tmp, &container) && + md_is_container_compatible(&ctx->containers[n_parents], &container)) { - MD_BLOCK* top_block = (MD_BLOCK*) ((char*)ctx->block_bytes + ctx->n_block_bytes - sizeof(MD_BLOCK)); - if(top_block->type == MD_BLOCK_LI) - n_parents--; - } + pivot_line = &md_dummy_blank_line; - ctx->last_list_item_starts_with_two_blank_lines = FALSE; - } -#endif - } + off = tmp; - /* Check whether we are Setext underline. */ - if(line->indent < ctx->code_indent_offset && pivot_line->type == MD_LINE_TEXT - && (CH(off) == _T('=') || CH(off) == _T('-')) - && (n_parents == ctx->n_containers)) - { - unsigned level; + total_indent += container.contents_indent - container.mark_indent; + line->indent = md_line_indentation(ctx, total_indent, off, &off); + total_indent += line->indent; + line->beg = off; + + /* Some of the following whitespace actually still belongs to the mark. */ + if(off >= ctx->size || ISNEWLINE(off)) { + container.contents_indent++; + } else if(line->indent <= ctx->code_indent_offset) { + container.contents_indent += line->indent; + line->indent = 0; + } else { + container.contents_indent += 1; + line->indent--; + } - if(md_is_setext_underline(ctx, off, &off, &level)) { - line->type = MD_LINE_SETEXTUNDERLINE; - line->data = level; - goto done; - } - } + ctx->containers[n_parents].mark_indent = container.mark_indent; + ctx->containers[n_parents].contents_indent = container.contents_indent; - /* Check for thematic break line. */ - if(line->indent < ctx->code_indent_offset && ISANYOF(off, _T("-_*"))) { - if(md_is_hr_line(ctx, off, &off)) { - line->type = MD_LINE_HR; - goto done; + n_brothers++; + continue; + } } - } - - /* Check whether we are table continuation. */ - if(pivot_line->type == MD_LINE_TABLE && md_is_table_row(ctx, off, &off)) { - line->type = MD_LINE_TABLE; - goto done; - } - /* Check for "brother" container. I.e. whether we are another list item - * in already started list. */ - if(n_parents < ctx->n_containers && n_brothers + n_children == 0) { - OFF tmp; + /* Check for indented code. + * Note indented code block cannot interrupt a paragraph. */ + if(line->indent >= ctx->code_indent_offset && + (pivot_line->type == MD_LINE_BLANK || pivot_line->type == MD_LINE_INDENTEDCODE)) + { + line->type = MD_LINE_INDENTEDCODE; + MD_ASSERT(line->indent >= ctx->code_indent_offset); + line->indent -= ctx->code_indent_offset; + line->data = 0; + break; + } - if(md_is_container_mark(ctx, line->indent, off, &tmp, &container) && - md_is_container_compatible(&ctx->containers[n_parents], &container)) + /* Check for start of a new container block. */ + if(line->indent < ctx->code_indent_offset && + md_is_container_mark(ctx, line->indent, off, &off, &container)) { - pivot_line = &md_dummy_blank_line; + if(pivot_line->type == MD_LINE_TEXT && n_parents == ctx->n_containers && + (off >= ctx->size || ISNEWLINE(off)) && container.ch != _T('>')) + { + /* Noop. List mark followed by a blank line cannot interrupt a paragraph. */ + } else if(pivot_line->type == MD_LINE_TEXT && n_parents == ctx->n_containers && + ISANYOF2_(container.ch, _T('.'), _T(')')) && container.start != 1) + { + /* Noop. Ordered list cannot interrupt a paragraph unless the start index is 1. */ + } else { + total_indent += container.contents_indent - container.mark_indent; + line->indent = md_line_indentation(ctx, total_indent, off, &off); + total_indent += line->indent; + + line->beg = off; + line->data = container.ch; + + /* Some of the following whitespace actually still belongs to the mark. */ + if(off >= ctx->size || ISNEWLINE(off)) { + container.contents_indent++; + } else if(line->indent <= ctx->code_indent_offset) { + container.contents_indent += line->indent; + line->indent = 0; + } else { + container.contents_indent += 1; + line->indent--; + } - off = tmp; + if(n_brothers + n_children == 0) + pivot_line = &md_dummy_blank_line; - total_indent += container.contents_indent - container.mark_indent; - line->indent = md_line_indentation(ctx, total_indent, off, &off); - total_indent += line->indent; - line->beg = off; + if(n_children == 0) + MD_CHECK(md_leave_child_containers(ctx, n_parents + n_brothers)); - /* Some of the following whitespace actually still belongs to the mark. */ - if(off >= ctx->size || ISNEWLINE(off)) { - container.contents_indent++; - } else if(line->indent <= ctx->code_indent_offset) { - container.contents_indent += line->indent; - line->indent = 0; - } else { - container.contents_indent += 1; - line->indent--; + n_children++; + MD_CHECK(md_push_container(ctx, &container)); + continue; } - - ctx->containers[n_parents].mark_indent = container.mark_indent; - ctx->containers[n_parents].contents_indent = container.contents_indent; - - n_brothers++; - goto redo; } - } - /* Check for indented code. - * Note indented code block cannot interrupt a paragraph. */ - if(line->indent >= ctx->code_indent_offset && - (pivot_line->type == MD_LINE_BLANK || pivot_line->type == MD_LINE_INDENTEDCODE)) - { - line->type = MD_LINE_INDENTEDCODE; - MD_ASSERT(line->indent >= ctx->code_indent_offset); - line->indent -= ctx->code_indent_offset; - line->data = 0; - goto done; - } + /* Check whether we are table continuation. */ + if(pivot_line->type == MD_LINE_TABLE && n_parents == ctx->n_containers) { + line->type = MD_LINE_TABLE; + break; + } - /* Check for start of a new container block. */ - if(line->indent < ctx->code_indent_offset && - md_is_container_mark(ctx, line->indent, off, &off, &container)) - { - if(pivot_line->type == MD_LINE_TEXT && n_parents == ctx->n_containers && - (off >= ctx->size || ISNEWLINE(off))) - { - /* Noop. List mark followed by a blank line cannot interrupt a paragraph. */ - } else if(pivot_line->type == MD_LINE_TEXT && n_parents == ctx->n_containers && - (container.ch == _T('.') || container.ch == _T(')')) && container.start != 1) + /* Check for ATX header. */ + if(line->indent < ctx->code_indent_offset && + off < ctx->size && CH(off) == _T('#')) { - /* Noop. Ordered list cannot interrupt a paragraph unless the start index is 1. */ - } else { - total_indent += container.contents_indent - container.mark_indent; - line->indent = md_line_indentation(ctx, total_indent, off, &off); - total_indent += line->indent; + unsigned level; - line->beg = off; - line->data = container.ch; - - /* Some of the following whitespace actually still belongs to the mark. */ - if(off >= ctx->size || ISNEWLINE(off)) { - container.contents_indent++; - } else if(line->indent <= ctx->code_indent_offset) { - container.contents_indent += line->indent; - line->indent = 0; - } else { - container.contents_indent += 1; - line->indent--; + if(md_is_atxheader_line(ctx, off, &line->beg, &off, &level)) { + line->type = MD_LINE_ATXHEADER; + line->data = level; + break; } - - if(n_brothers + n_children == 0) - pivot_line = &md_dummy_blank_line; - - if(n_children == 0) - MD_CHECK(md_leave_child_containers(ctx, n_parents + n_brothers)); - - n_children++; - MD_CHECK(md_push_container(ctx, &container)); - goto redo; } - } - /* Check for ATX header. */ - if(line->indent < ctx->code_indent_offset && CH(off) == _T('#')) { - unsigned level; - - if(md_is_atxheader_line(ctx, off, &line->beg, &off, &level)) { - line->type = MD_LINE_ATXHEADER; - line->data = level; - goto done; + /* Check whether we are starting code fence. */ + if(off < ctx->size && ISANYOF2(off, _T('`'), _T('~'))) { + if(md_is_opening_code_fence(ctx, off, &off)) { + line->type = MD_LINE_FENCEDCODE; + line->data = 1; + break; + } } - } - /* Check whether we are starting code fence. */ - if(CH(off) == _T('`') || CH(off) == _T('~')) { - if(md_is_opening_code_fence(ctx, off, &off)) { - line->type = MD_LINE_FENCEDCODE; - line->data = 1; - goto done; - } - } + /* Check for start of raw HTML block. */ + if(off < ctx->size && CH(off) == _T('<') + && !(ctx->parser.flags & MD_FLAG_NOHTMLBLOCKS)) + { + ctx->html_block_type = md_is_html_block_start_condition(ctx, off); - /* Check for start of raw HTML block. */ - if(CH(off) == _T('<') && !(ctx->r.flags & MD_FLAG_NOHTMLBLOCKS)) - { - ctx->html_block_type = md_is_html_block_start_condition(ctx, off); + /* HTML block type 7 cannot interrupt paragraph. */ + if(ctx->html_block_type == 7 && pivot_line->type == MD_LINE_TEXT) + ctx->html_block_type = 0; - /* HTML block type 7 cannot interrupt paragraph. */ - if(ctx->html_block_type == 7 && pivot_line->type == MD_LINE_TEXT) - ctx->html_block_type = 0; + if(ctx->html_block_type > 0) { + /* The line itself also may immediately close the block. */ + if(md_is_html_block_end_condition(ctx, off, &off) == ctx->html_block_type) { + /* Make sure this is the last line of the block. */ + ctx->html_block_type = 0; + } - if(ctx->html_block_type > 0) { - /* The line itself also may immediately close the block. */ - if(md_is_html_block_end_condition(ctx, off, &off) == ctx->html_block_type) { - /* Make sure this is the last line of the block. */ - ctx->html_block_type = 0; + line->type = MD_LINE_HTML; + break; } + } + + /* Check for table underline. */ + if((ctx->parser.flags & MD_FLAG_TABLES) && pivot_line->type == MD_LINE_TEXT + && off < ctx->size && ISANYOF3(off, _T('|'), _T('-'), _T(':')) + && n_parents == ctx->n_containers) + { + unsigned col_count; - line->type = MD_LINE_HTML; - goto done; + if(ctx->current_block != NULL && ctx->current_block->n_lines == 1 && + md_is_table_underline(ctx, off, &off, &col_count)) + { + line->data = col_count; + line->type = MD_LINE_TABLEUNDERLINE; + break; + } } - } - /* Check for table underline. */ - if((ctx->r.flags & MD_FLAG_TABLES) && pivot_line->type == MD_LINE_TEXT && - (CH(off) == _T('|') || CH(off) == _T('-') || CH(off) == _T(':'))) - { - unsigned col_count; + /* By default, we are normal text line. */ + line->type = MD_LINE_TEXT; + if(pivot_line->type == MD_LINE_TEXT && n_brothers + n_children == 0) { + /* Lazy continuation. */ + n_parents = ctx->n_containers; + } - if(ctx->current_block != NULL && ctx->current_block->n_lines == 1 && - md_is_table_underline(ctx, off, &off, &col_count) && - md_is_table_row(ctx, pivot_line->beg, NULL)) + /* Check for task mark. */ + if((ctx->parser.flags & MD_FLAG_TASKLISTS) && n_brothers + n_children > 0 && + ISANYOF_(ctx->containers[ctx->n_containers-1].ch, _T("-+*.)"))) { - line->data = col_count; - line->type = MD_LINE_TABLEUNDERLINE; - goto done; + OFF tmp = off; + + while(tmp < ctx->size && tmp < off + 3 && ISBLANK(tmp)) + tmp++; + if(tmp + 2 < ctx->size && CH(tmp) == _T('[') && + ISANYOF(tmp+1, _T("xX ")) && CH(tmp+2) == _T(']') && + (tmp + 3 == ctx->size || ISBLANK(tmp+3) || ISNEWLINE(tmp+3))) + { + MD_CONTAINER* task_container = (n_children > 0 ? &ctx->containers[ctx->n_containers-1] : &container); + task_container->is_task = TRUE; + task_container->task_mark_off = tmp + 1; + off = tmp + 3; + while(off < ctx->size && ISWHITESPACE(off)) + off++; + if (off == ctx->size) break; + line->beg = off; + } } - } - /* By default, we are normal text line. */ - line->type = MD_LINE_TEXT; - if(pivot_line->type == MD_LINE_TEXT && n_brothers + n_children == 0) { - /* Lazy continuation. */ - n_parents = ctx->n_containers; + break; } -done: /* Scan for end of the line. * - * Note this is bottleneck of this function as we itereate over (almost) - * all line contents after some initial line indentation. To optimize, we - * try to eat multiple chars in every loop iteration. - * - * (Measured ~6% performance boost of md2html with this optimization for - * normal kind of input.) + * Note this is quite a bottleneck of the parsing as we here iterate almost + * over compete document. */ - while(off + 4 < ctx->size && !ISNEWLINE(off+0) && !ISNEWLINE(off+1) - && !ISNEWLINE(off+2) && !ISNEWLINE(off+3)) - off += 4; - while(off < ctx->size && !ISNEWLINE(off)) - off++; +#if defined __linux__ && !defined MD4C_USE_UTF16 + /* Recent glibc versions have superbly optimized strcspn(), even using + * vectorization if available. */ + if(ctx->doc_ends_with_newline && off < ctx->size) { + while(TRUE) { + off += (OFF) strcspn(STR(off), "\r\n"); + + /* strcspn() can stop on zero terminator; but that can appear + * anywhere in the Markfown input... */ + if(CH(off) == _T('\0')) + off++; + else + break; + } + } else +#endif + { + /* Optimization: Use some loop unrolling. */ + while(off + 3 < ctx->size && !ISNEWLINE(off+0) && !ISNEWLINE(off+1) + && !ISNEWLINE(off+2) && !ISNEWLINE(off+3)) + off += 4; + while(off < ctx->size && !ISNEWLINE(off)) + off++; + } -done_on_eol: /* Set end of the line. */ line->end = off; @@ -5717,7 +6193,7 @@ done_on_eol: tmp--; while(tmp > line->beg && CH(tmp-1) == _T('#')) tmp--; - if(tmp == line->beg || CH(tmp-1) == _T(' ') || (ctx->r.flags & MD_FLAG_PERMISSIVEATXHEADERS)) + if(tmp == line->beg || CH(tmp-1) == _T(' ') || (ctx->parser.flags & MD_FLAG_PERMISSIVEATXHEADERS)) line->end = tmp; } @@ -5751,19 +6227,27 @@ done_on_eol: /* Enter any container we found a mark for. */ if(n_brothers > 0) { MD_ASSERT(n_brothers == 1); - MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI, 0, 0, - MD_BLOCK_CONTAINER_CLOSER | MD_BLOCK_CONTAINER_OPENER)); + MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI, + ctx->containers[n_parents].task_mark_off, + (ctx->containers[n_parents].is_task ? CH(ctx->containers[n_parents].task_mark_off) : 0), + MD_BLOCK_CONTAINER_CLOSER)); + MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI, + container.task_mark_off, + (container.is_task ? CH(container.task_mark_off) : 0), + MD_BLOCK_CONTAINER_OPENER)); + ctx->containers[n_parents].is_task = container.is_task; + ctx->containers[n_parents].task_mark_off = container.task_mark_off; } if(n_children > 0) - MD_CHECK(md_enter_child_containers(ctx, n_children, line->data)); + MD_CHECK(md_enter_child_containers(ctx, n_children)); abort: return ret; } static int -md_process_line(MD_CTX* ctx, const MD_LINE_ANALYSIS** p_pivot_line, const MD_LINE_ANALYSIS* line) +md_process_line(MD_CTX* ctx, const MD_LINE_ANALYSIS** p_pivot_line, MD_LINE_ANALYSIS* line) { const MD_LINE_ANALYSIS* pivot_line = *p_pivot_line; int ret = 0; @@ -5792,8 +6276,17 @@ md_process_line(MD_CTX* ctx, const MD_LINE_ANALYSIS** p_pivot_line, const MD_LIN MD_ASSERT(ctx->current_block != NULL); ctx->current_block->type = MD_BLOCK_H; ctx->current_block->data = line->data; + ctx->current_block->flags |= MD_BLOCK_SETEXT_HEADER; + MD_CHECK(md_add_line_into_current_block(ctx, line)); MD_CHECK(md_end_current_block(ctx)); - *p_pivot_line = &md_dummy_blank_line; + if(ctx->current_block == NULL) { + *p_pivot_line = &md_dummy_blank_line; + } else { + /* This happens if we have consumed all the body as link ref. defs. + * and downgraded the underline into start of a new paragraph block. */ + line->type = MD_LINE_TEXT; + *p_pivot_line = line; + } return 0; } @@ -5888,23 +6381,28 @@ abort: ********************/ int -md_parse(const MD_CHAR* text, MD_SIZE size, const MD_RENDERER* renderer, void* userdata) +md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userdata) { - MD_CTX ctx; + MD_CTX ctx = {.text = text, + .size = size, + .userdata = userdata, + .code_indent_offset = (ctx.parser.flags & MD_FLAG_NOINDENTEDCODEBLOCKS) ? (OFF)(-1) : 4, + .doc_ends_with_newline = (size > 0 && ISNEWLINE_(text[size-1]))}; int i; int ret; + if(parser->abi_version != 0) { + if(parser->debug_log != NULL) + parser->debug_log("Unsupported abi_version.", userdata); + return -1; + } + /* Setup context structure. */ - memset(&ctx, 0, sizeof(MD_CTX)); - ctx.text = text; - ctx.size = size; - memcpy(&ctx.r, renderer, sizeof(MD_RENDERER)); - ctx.userdata = userdata; - ctx.code_indent_offset = (ctx.r.flags & MD_FLAG_NOINDENTEDCODEBLOCKS) ? (OFF)(-1) : 4; + memcpy(&ctx.parser, parser, sizeof(MD_PARSER)); md_build_mark_char_map(&ctx); /* Reset all unresolved opener mark chains. */ - for(i = 0; i < SIZEOF_ARRAY(ctx.mark_chains); i++) { + for(i = 0; i < (int) SIZEOF_ARRAY(ctx.mark_chains); i++) { ctx.mark_chains[i].head = -1; ctx.mark_chains[i].tail = -1; } diff --git a/md4c/md4c.h b/md4c/md4c.h @@ -2,7 +2,7 @@ * MD4C: Markdown parser for C * (http://github.com/mity/md4c) * - * Copyright (c) 2016-2017 Martin Mitas + * Copyright (c) 2016-2020 Martin Mitas * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -23,26 +23,22 @@ * IN THE SOFTWARE. */ -#ifndef MD4C_MARKDOWN_H -#define MD4C_MARKDOWN_H +#ifndef MD4C_H +#define MD4C_H #ifdef __cplusplus extern "C" { #endif - -#define MD_VERSION_MAJOR 0 -#define MD_VERSION_MINOR 2 -#define MD_VERSION_RELEASE 3 - - -/* Magic to support UTF-16. */ #if defined MD4C_USE_UTF16 + /* Magic to support UTF-16. Note that in order to use it, you have to define + * the macro MD4C_USE_UTF16 both when building MD4C as well as when + * including this header in your code. */ #ifdef _WIN32 - #include <wchar.h> + #include <windows.h> typedef WCHAR MD_CHAR; #else - #error MD4C_USE_UTF16 is only upported on Windows. + #error MD4C_USE_UTF16 is only supported on Windows. #endif #else typedef char MD_CHAR; @@ -53,7 +49,8 @@ typedef unsigned MD_OFFSET; /* Block represents a part of document hierarchy structure like a paragraph - * or list item. */ + * or list item. + */ typedef enum MD_BLOCKTYPE { /* <body>...</body> */ MD_BLOCK_DOC = 0, @@ -69,7 +66,8 @@ typedef enum MD_BLOCKTYPE { * Detail: Structure MD_BLOCK_OL_DETAIL. */ MD_BLOCK_OL, - /* <li>...</li> */ + /* <li>...</li> + * Detail: Structure MD_BLOCK_LI_DETAIL. */ MD_BLOCK_LI, /* <hr> */ @@ -93,7 +91,8 @@ typedef enum MD_BLOCKTYPE { MD_BLOCK_P, /* <table>...</table> and its contents. - * Detail: Structure MD_BLOCK_TD_DETAIL (used with MD_BLOCK_TH and MD_BLOCK_TD) + * Detail: Structure MD_BLOCK_TABLE_DETAIL (for MD_BLOCK_TABLE), + * structure MD_BLOCK_TD_DETAIL (for MD_BLOCK_TH and MD_BLOCK_TD) * Note all of these are used only if extension MD_FLAG_TABLES is enabled. */ MD_BLOCK_TABLE, MD_BLOCK_THEAD, @@ -121,7 +120,7 @@ typedef enum MD_SPANTYPE { * Detail: Structure MD_SPAN_IMG_DETAIL. * Note: Image text can contain nested spans and even nested images. * If rendered into ALT attribute of HTML <IMG> tag, it's responsibility - * of the renderer to deal with it. + * of the parser to deal with it. */ MD_SPAN_IMG, @@ -131,7 +130,22 @@ typedef enum MD_SPANTYPE { /* <del>...</del> * Note: Recognized only when MD_FLAG_STRIKETHROUGH is enabled. */ - MD_SPAN_DEL + MD_SPAN_DEL, + + /* For recognizing inline ($) and display ($$) equations + * Note: Recognized only when MD_FLAG_LATEXMATHSPANS is enabled. + */ + MD_SPAN_LATEXMATH, + MD_SPAN_LATEXMATH_DISPLAY, + + /* Wiki links + * Note: Recognized only when MD_FLAG_WIKILINKS is enabled. + */ + MD_SPAN_WIKILINK, + + /* <u>...</u> + * Note: Recognized only when MD_FLAG_UNDERLINE is enabled. */ + MD_SPAN_U } MD_SPANTYPE; /* Text is the actual textual contents of span. */ @@ -158,7 +172,7 @@ typedef enum MD_TEXTTYPE { * (c) Hexadecimal entity, e.g. &#x12AB; * * As MD4C is mostly encoding agnostic, application gets the verbatim - * entity text into the MD_RENDERER::text_callback(). */ + * entity text into the MD_PARSER::text_callback(). */ MD_TEXT_ENTITY, /* Text in a code block (inside MD_BLOCK_CODE) or inlined code (`code`). @@ -170,7 +184,11 @@ typedef enum MD_TEXTTYPE { /* Text is a raw HTML. If it is contents of a raw HTML block (i.e. not * an inline raw HTML), then MD_TEXT_BR and MD_TEXT_SOFTBR are not used. * The text contains verbatim '\n' for the new lines. */ - MD_TEXT_HTML + MD_TEXT_HTML, + + /* Text is inside an equation. This is processed the same way as inlined code + * spans (`code`). */ + MD_TEXT_LATEXMATH } MD_TEXTTYPE; @@ -189,19 +207,26 @@ typedef enum MD_ALIGN { * propagated within various detailed structures, but which still may contain * string portions of different types like e.g. entities. * - * So, for example, lets consider an image has a title attribute string - * set to "foo &quot; bar". (Note the string size is 14.) + * So, for example, lets consider this image: + * + * ![image alt text](http://example.org/image.png 'foo &quot; bar') * - * Then: + * The image alt text is propagated as a normal text via the MD_PARSER::text() + * callback. However, the image title ('foo &quot; bar') is propagated as + * MD_ATTRIBUTE in MD_SPAN_IMG_DETAIL::title. + * + * Then the attribute MD_SPAN_IMG_DETAIL::title shall provide the following: * -- [0]: "foo " (substr_types[0] == MD_TEXT_NORMAL; substr_offsets[0] == 0) * -- [1]: "&quot;" (substr_types[1] == MD_TEXT_ENTITY; substr_offsets[1] == 4) * -- [2]: " bar" (substr_types[2] == MD_TEXT_NORMAL; substr_offsets[2] == 10) * -- [3]: (n/a) (n/a ; substr_offsets[3] == 14) * - * Note that these conditions are guaranteed: + * Note that these invariants are always guaranteed: * -- substr_offsets[0] == 0 * -- substr_offsets[LAST+1] == size - * -- Only MD_TEXT_NORMAL, MD_TEXT_ENTITY, MD_TEXT_NULLCHAR substrings can appear. + * -- Currently, only MD_TEXT_NORMAL, MD_TEXT_ENTITY, MD_TEXT_NULLCHAR + * substrings can appear. This could change only of the specification + * changes. */ typedef struct MD_ATTRIBUTE { const MD_CHAR* text; @@ -211,19 +236,26 @@ typedef struct MD_ATTRIBUTE { } MD_ATTRIBUTE; -/* Detailed info for MD_BLOCK_UL_DETAIL. */ +/* Detailed info for MD_BLOCK_UL. */ typedef struct MD_BLOCK_UL_DETAIL { - int is_tight; /* Non-zero if tight list, zero of loose. */ + int is_tight; /* Non-zero if tight list, zero if loose. */ MD_CHAR mark; /* Item bullet character in MarkDown source of the list, e.g. '-', '+', '*'. */ } MD_BLOCK_UL_DETAIL; -/* Detailed info for MD_BLOCK_OL_DETAIL. */ +/* Detailed info for MD_BLOCK_OL. */ typedef struct MD_BLOCK_OL_DETAIL { unsigned start; /* Start index of the ordered list. */ - int is_tight; /* Non-zero if tight list, zero of loose. */ + int is_tight; /* Non-zero if tight list, zero if loose. */ MD_CHAR mark_delimiter; /* Character delimiting the item marks in MarkDown source, e.g. '.' or ')' */ } MD_BLOCK_OL_DETAIL; +/* Detailed info for MD_BLOCK_LI. */ +typedef struct MD_BLOCK_LI_DETAIL { + int is_task; /* Can be non-zero only with MD_FLAG_TASKLISTS */ + MD_CHAR task_mark; /* If is_task, then one of 'x', 'X' or ' '. Undefined otherwise. */ + MD_OFFSET task_mark_offset; /* If is_task, then offset in the input of the char between '[' and ']'. */ +} MD_BLOCK_LI_DETAIL; + /* Detailed info for MD_BLOCK_H. */ typedef struct MD_BLOCK_H_DETAIL { unsigned level; /* Header level (1 - 6) */ @@ -233,8 +265,16 @@ typedef struct MD_BLOCK_H_DETAIL { typedef struct MD_BLOCK_CODE_DETAIL { MD_ATTRIBUTE info; MD_ATTRIBUTE lang; + MD_CHAR fence_char; /* The character used for fenced code block; or zero for indented code block. */ } MD_BLOCK_CODE_DETAIL; +/* Detailed info for MD_BLOCK_TABLE. */ +typedef struct MD_BLOCK_TABLE_DETAIL { + unsigned col_count; /* Count of columns in the table. */ + unsigned head_row_count; /* Count of rows in the table header (currently always 1) */ + unsigned body_row_count; /* Count of rows in the table body */ +} MD_BLOCK_TABLE_DETAIL; + /* Detailed info for MD_BLOCK_TH and MD_BLOCK_TD. */ typedef struct MD_BLOCK_TD_DETAIL { MD_ALIGN align; @@ -252,10 +292,14 @@ typedef struct MD_SPAN_IMG_DETAIL { MD_ATTRIBUTE title; } MD_SPAN_IMG_DETAIL; +/* Detailed info for MD_SPAN_WIKILINK. */ +typedef struct MD_SPAN_WIKILINK { + MD_ATTRIBUTE target; +} MD_SPAN_WIKILINK_DETAIL; /* Flags specifying extensions/deviations from CommonMark specification. * - * By default (when MD_RENDERER::flags == 0), we follow CommonMark specification. + * By default (when MD_PARSER::flags == 0), we follow CommonMark specification. * The following flags may allow some extensions or deviations from it. */ #define MD_FLAG_COLLAPSEWHITESPACE 0x0001 /* In MD_TEXT_NORMAL, collapse non-trivial whitespace into single ' ' */ @@ -268,21 +312,37 @@ typedef struct MD_SPAN_IMG_DETAIL { #define MD_FLAG_TABLES 0x0100 /* Enable tables extension. */ #define MD_FLAG_STRIKETHROUGH 0x0200 /* Enable strikethrough extension. */ #define MD_FLAG_PERMISSIVEWWWAUTOLINKS 0x0400 /* Enable WWW autolinks (even without any scheme prefix, if they begin with 'www.') */ +#define MD_FLAG_TASKLISTS 0x0800 /* Enable task list extension. */ +#define MD_FLAG_LATEXMATHSPANS 0x1000 /* Enable $ and $$ containing LaTeX equations. */ +#define MD_FLAG_WIKILINKS 0x2000 /* Enable wiki links extension. */ +#define MD_FLAG_UNDERLINE 0x4000 /* Enable underline extension (and disables '_' for normal emphasis). */ #define MD_FLAG_PERMISSIVEAUTOLINKS (MD_FLAG_PERMISSIVEEMAILAUTOLINKS | MD_FLAG_PERMISSIVEURLAUTOLINKS | MD_FLAG_PERMISSIVEWWWAUTOLINKS) #define MD_FLAG_NOHTML (MD_FLAG_NOHTMLBLOCKS | MD_FLAG_NOHTMLSPANS) /* Convenient sets of flags corresponding to well-known Markdown dialects. + * * Note we may only support subset of features of the referred dialect. * The constant just enables those extensions which bring us as close as * possible given what features we implement. + * + * ABI compatibility note: Meaning of these can change in time as new + * extensions, bringing the dialect closer to the original, are implemented. */ #define MD_DIALECT_COMMONMARK 0 -#define MD_DIALECT_GITHUB (MD_FLAG_PERMISSIVEAUTOLINKS | MD_FLAG_TABLES | MD_FLAG_STRIKETHROUGH) +#define MD_DIALECT_GITHUB (MD_FLAG_PERMISSIVEAUTOLINKS | MD_FLAG_TABLES | MD_FLAG_STRIKETHROUGH | MD_FLAG_TASKLISTS) -/* Renderer structure. +/* Parser structure. */ -typedef struct MD_RENDERER { +typedef struct MD_PARSER { + /* Reserved. Set to zero. + */ + unsigned abi_version; + + /* Dialect options. Bitmask of MD_FLAG_xxxx values. + */ + unsigned flags; + /* Caller-provided rendering callbacks. * * For some block/span types, more detailed information is provided in a @@ -293,9 +353,10 @@ typedef struct MD_RENDERER { * * Note any strings provided to the callbacks as their arguments or as * members of any detail structure are generally not zero-terminated. - * Application has take the respective size information into account. + * Application has to take the respective size information into account. * - * Callbacks may abort further parsing of the document by returning non-zero. + * Any rendering callback may abort further parsing of the document by + * returning non-zero. */ int (*enter_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/); int (*leave_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/); @@ -314,26 +375,31 @@ typedef struct MD_RENDERER { */ void (*debug_log)(const char* /*msg*/, void* /*userdata*/); - /* Dialect options. Bitmask of MD_FLAG_xxxx values. + /* Reserved. Set to NULL. */ - unsigned flags; -} MD_RENDERER; + void (*syntax)(void); +} MD_PARSER; + + +/* For backward compatibility. Do not use in new code. + */ +typedef MD_PARSER MD_RENDERER; /* Parse the Markdown document stored in the string 'text' of size 'size'. - * The renderer provides callbacks to be called during the parsing so the + * The parser provides callbacks to be called during the parsing so the * caller can render the document on the screen or convert the Markdown * to another format. * * Zero is returned on success. If a runtime error occurs (e.g. a memory * fails), -1 is returned. If the processing is aborted due any callback - * returning non-zero, md_parse() the return value of the callback is returned. + * returning non-zero, the return value of the callback is returned. */ -int md_parse(const MD_CHAR* text, MD_SIZE size, const MD_RENDERER* renderer, void* userdata); +int md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userdata); #ifdef __cplusplus } /* extern "C" { */ #endif -#endif /* MD4C_MARKDOWN_H */ +#endif /* MD4C_H */