diff options
Diffstat (limited to 'src/3rdparty')
-rw-r--r-- | src/3rdparty/md4c/md4c.c | 2319 | ||||
-rw-r--r-- | src/3rdparty/md4c/md4c.h | 7 | ||||
-rw-r--r-- | src/3rdparty/md4c/qt_attribution.json | 4 |
3 files changed, 1209 insertions, 1121 deletions
diff --git a/src/3rdparty/md4c/md4c.c b/src/3rdparty/md4c/md4c.c index 13c7bd3433..01e63a5fd2 100644 --- a/src/3rdparty/md4c/md4c.c +++ b/src/3rdparty/md4c/md4c.c @@ -98,6 +98,9 @@ struct MD_CTX_tag { 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; @@ -124,17 +127,20 @@ struct MD_CTX_tag { #endif /* For resolving of inline spans. */ - MD_MARKCHAIN mark_chains[8]; -#define PTR_CHAIN ctx->mark_chains[0] -#define TABLECELLBOUNDARIES ctx->mark_chains[1] -#define BACKTICK_OPENERS ctx->mark_chains[2] -#define LOWERTHEN_OPENERS ctx->mark_chains[3] -#define ASTERISK_OPENERS ctx->mark_chains[4] -#define UNDERSCORE_OPENERS ctx->mark_chains[5] -#define TILDE_OPENERS ctx->mark_chains[6] -#define BRACKET_OPENERS ctx->mark_chains[7] -#define OPENERS_CHAIN_FIRST 2 -#define OPENERS_CHAIN_LAST 7 + MD_MARKCHAIN mark_chains[11]; +#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 ctx->mark_chains[9] +#define BRACKET_OPENERS ctx->mark_chains[10] +#define OPENERS_CHAIN_FIRST 2 +#define OPENERS_CHAIN_LAST 10 int n_table_cell_boundaries; @@ -142,6 +148,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 @@ -159,18 +171,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, @@ -184,6 +194,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 { @@ -445,209 +456,212 @@ 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]; + unsigned codepoints[3]; int 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), 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), 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), + S(0x1145b), S(0x1145d), S(0x114c6), R(0x115c1,0x115d7), R(0x11641,0x11643), R(0x11660,0x1166c), + R(0x1173c,0x1173e), S(0x1183b), 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_punct_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(0x0186), S(0x0187), S(0x0189), 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(0x01b3), 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(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(0x0376), S(0x037f), S(0x0386), R(0x0388,0x038a), S(0x038c), S(0x038e), 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(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(0x1fba), S(0x1fbe), R(0x1fc8,0x1fcb), S(0x1fd8), S(0x1fda), S(0x1fe8), + S(0x1fea), S(0x1fec), S(0x1ff8), S(0x1ffa), 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), + R(0x2c80,0x2ce2), S(0x2ceb), S(0x2cf2), R(0xa640,0xa66c), R(0xa680,0xa69a), R(0xa722,0xa72e), + R(0xa732,0xa76e), S(0xa779), S(0xa77d), R(0xa77e,0xa786), S(0xa78b), S(0xa78d), S(0xa790), + 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), 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, 0x0254, 0x0188, 0x0256, 0x018c, 0x01dd, + 0x0259, 0x025b, 0x0192, 0x0260, 0x0263, 0x0269, 0x0268, 0x0199, 0x026f, 0x0272, 0x0275, 0x01a1, 0x01a5, + 0x0280, 0x01a8, 0x0283, 0x01ad, 0x0288, 0x01b0, 0x028a, 0x01b4, 0x0292, 0x01b9, 0x01bd, 0x01c6, 0x01c6, + 0x01c9, 0x01c9, 0x01cc, 0x01cc, 0x01dc, 0x01df, 0x01ef, 0x01f3, 0x01f3, 0x0195, 0x01bf, 0x01f9, 0x021f, + 0x019e, 0x0223, 0x0233, 0x2c65, 0x023c, 0x019a, 0x2c66, 0x0242, 0x0180, 0x0289, 0x028c, 0x0247, 0x024f, + 0x03b9, 0x0371, 0x0377, 0x03f3, 0x03ac, 0x03ad, 0x03af, 0x03cc, 0x03cd, 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, 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, 0x1f70, 0x03b9, 0x1f72, 0x1f75, 0x1fd0, 0x1f76, + 0x1fe0, 0x1f7a, 0x1fe5, 0x1f78, 0x1f7c, 0x03c9, 0x006b, 0x00e5, 0x214e, 0x2170, 0x217f, 0x2184, 0x24d0, + 0x24e9, 0x2c30, 0x2c5e, 0x2c61, 0x026b, 0x1d7d, 0x027d, 0x2c68, 0x2c6c, 0x0251, 0x0271, 0x0250, 0x0252, + 0x2c73, 0x2c76, 0x023f, 0x2c81, 0x2ce3, 0x2cec, 0x2cf3, 0xa641, 0xa66d, 0xa681, 0xa69b, 0xa723, 0xa72f, + 0xa733, 0xa76f, 0xa77a, 0x1d79, 0xa77f, 0xa787, 0xa78c, 0x0265, 0xa791, 0xa797, 0xa7a9, 0x0266, 0x025c, + 0x0261, 0x026c, 0x026a, 0x029e, 0x0287, 0x029d, 0xab53, 0xa7b5, 0xa7bf, 0xa7c3, 0xa794, 0x0282, 0x1d8e, + 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; + int 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; @@ -661,41 +675,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. */ + int 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; } @@ -707,7 +717,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])) { @@ -723,7 +733,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))) @@ -752,7 +762,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])) { @@ -788,10 +798,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))) { @@ -811,7 +821,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) @@ -821,7 +831,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); @@ -835,7 +845,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)) @@ -843,11 +853,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 @@ -921,7 +931,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); @@ -1064,108 +1074,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 contyain "--", 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; @@ -1182,31 +1177,8 @@ 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; - } - - 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; - - *p_end = off+1; - return TRUE; + return md_scan_for_html_closer(ctx, _T(">"), 1, + lines, n_lines, off, max_end, p_end, &ctx->html_decl_horizon); } static int @@ -1216,7 +1188,6 @@ md_is_html_cdata(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF ma static const SZ open_size = SIZEOF_ARRAY(open_str) - 1; OFF off = beg; - int i = 0; if(off + open_size >= lines[0].end) return FALSE; @@ -1224,49 +1195,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)); } @@ -1282,7 +1226,7 @@ md_is_hex_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, O 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 { @@ -1298,7 +1242,7 @@ md_is_dec_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, O 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 { @@ -1551,7 +1495,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); @@ -1563,25 +1507,53 @@ 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 link label as a whitespace. */ + goto whitespace; + } + + if(ISNEWLINE_(label[off])) { + /* Treat new lines as a whitespace. */ + off++; + goto whitespace; } - return hash; + codepoint = md_decode_unicode(label, off, size, &char_size); + off += char_size; + if(ISUNICODEWHITESPACE_(codepoint)) { + /* Treat all whitespace as equivalent */ + goto whitespace; + } + + /* 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 off; } static int @@ -1589,55 +1561,35 @@ md_link_label_cmp(const CHAR* a_label, SZ a_size, const CHAR* b_label, SZ b_size { OFF a_off; OFF b_off; + int a_reached_end = FALSE; + int b_reached_end = FALSE; + MD_UNICODE_FOLD_INFO a_fi = { 0 }; + MD_UNICODE_FOLD_INFO b_fi = { 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_reached_end && !b_reached_end) { + /* 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); + a_reached_end = (a_off >= a_size); } - - 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); + b_reached_end = (b_off >= b_size); } - 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; @@ -1911,7 +1863,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); @@ -1958,7 +1910,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('>')) { @@ -2017,6 +1969,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, @@ -2026,7 +1988,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) { @@ -2035,6 +1997,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; @@ -2061,6 +2025,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++; @@ -2081,9 +2048,7 @@ md_is_link_title(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, * 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; @@ -2127,8 +2092,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 @@ -2214,16 +2179,6 @@ abort: 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); - return ret; -} - static int md_is_link_reference(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF end, MD_LINK_ATTR* attr) @@ -2279,9 +2234,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; @@ -2321,7 +2275,7 @@ md_is_inline_link_spec_helper(MD_CTX* ctx, const MD_LINE* lines, int n_lines, } /* Link destination. */ - if(!is_link_dest_fn(ctx, off, lines[line_index].end, + if(!md_is_link_destination(ctx, off, lines[line_index].end, &off, &attr->dest_beg, &attr->dest_end)) return FALSE; @@ -2376,14 +2330,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) { @@ -2493,13 +2439,42 @@ 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. */ +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 &TILDE_OPENERS; + case _T('['): return &BRACKET_OPENERS; + case _T('|'): return &TABLECELLBOUNDARIES; + default: return NULL; + } +} static MD_MARK* md_push_mark(MD_CTX* ctx) @@ -2658,18 +2633,11 @@ 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; } } @@ -2730,19 +2698,250 @@ md_build_mark_char_map(MD_CTX* ctx) 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 then 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_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) +{ + 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; + + 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); + + /* The caller needs to know end of the opening mark even if we fail. */ + *p_opener_end = opener_end; + + mark_len = opener_end - opener_beg; + if(mark_len > CODESPAN_MARK_MAXLEN) + return FALSE; + + /* 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; + + closer_beg = opener_end; + closer_end = opener_end; + + /* 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++; + + 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(closer_end - closer_beg > 0) { + /* We have found a back-tick which is not part of the closer. */ + has_only_space = FALSE; + + /* 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; + } + } + + 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 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++; + } + } + + *p_opener_beg = opener_beg; + *p_opener_end = opener_end; + *p_closer_beg = closer_beg; + *p_closer_end = closer_end; + return TRUE; +} + +static int +md_is_autolink_uri(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end) +{ + OFF off = beg+1; + + MD_ASSERT(CH(beg) == _T('<')); + + /* 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 - 62 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 > 62) + 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) { int i; int ret = 0; MD_MARK* mark; + OFF codespan_last_potential_closers[CODESPAN_MARK_MAXLEN] = { 0 }; + int codespan_scanned_till_paragraph_end = FALSE; for(i = 0; i < n_lines; i++) { const MD_LINE* line = &lines[i]; @@ -2761,8 +2960,8 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) #define IS_MARK_CHAR(off) (ctx->mark_char_map[(unsigned char) CH(off)]) #endif - /* Optimization: Fast path (with some loop unrolling). */ - while(off + 4 < line_end && !IS_MARK_CHAR(off+0) && !IS_MARK_CHAR(off+1) + /* 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)) @@ -2780,14 +2979,7 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) /* 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 '`' 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; + off += 2; continue; } @@ -2835,9 +3027,9 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) * 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); @@ -2860,18 +3052,32 @@ 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, lines + i, n_lines - i, 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. */ + while(off > line_end) { + i++; + line++; + line_end = line->end; + } + continue; + } - off = tmp; + off = opener_end; continue; } @@ -2893,9 +3099,49 @@ 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->parser.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, lines + i, n_lines - i, 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. */ + while(off > line_end) { + i++; + 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; @@ -2947,7 +3193,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; @@ -3002,6 +3248,7 @@ md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode) PUSH_MARK(ch, off, tmp, MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER); off = tmp; + continue; } /* Turn non-trivial whitespace into single space. */ @@ -3037,231 +3284,6 @@ 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) { @@ -3501,7 +3523,7 @@ 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 - n); @@ -3518,79 +3540,77 @@ 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(mark->flags & MD_MARK_POTENTIAL_CLOSER) { + MD_MARK* opener = NULL; + int opener_index; + + if(mark->ch == _T('*')) { + MD_MARKCHAIN* opener_chains[6]; + int i, n_opener_chains; + unsigned flags = mark->flags; + + /* Apply "rule of three". (This is why we break asterisk opener + * marks into multiple chains.) */ + 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_orig_size_modulo3 + closer_size) % 3 != 0) { - /* This opener is suitable. */ - break; - } + /* Resolve, if we have found matching opener. */ + if(opener != NULL) { + SZ opener_size = opener->end - opener->beg; + SZ closer_size = mark->end - mark->beg; - 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(opener_size > closer_size) { + opener_index = md_split_emph_mark(ctx, opener_index, closer_size); + md_mark_chain_append(ctx, md_mark_chain(ctx, opener_index), opener_index); + } else if(opener_size < closer_size) { + md_split_emph_mark(ctx, mark_index, closer_size - opener_size); } - } - 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); + 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, 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) { @@ -3618,55 +3638,57 @@ md_analyze_permissive_url_autolink(MD_CTX* ctx, int mark_index) 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; /* 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)) - off++; - - /* Path validation. */ - if(ISANYOF(off-1, _T("?!.,:*_~)"))) { - if(CH(off-1) != _T(')')) { - off--; - } 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--; + 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 + break; } + + off++; } + /* These cannot be last char In such case they are more likely normal + * punctuation. */ + if(ISANYOF(off-1, _T("?!.,:*_~"))) + off--; /* Ok. Lets call it auto-link. Adapt opener and create closer to zero * length so all the contents becomes the link text. */ @@ -3755,16 +3777,13 @@ 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 '.': /* Pass through. */ case ':': md_analyze_permissive_url_autolink(ctx, i); break; @@ -3789,11 +3808,7 @@ md_analyze_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mod /* 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; + md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("&")); if(table_mode) { /* (2) Analyze table cell boundaries. @@ -3827,8 +3842,18 @@ 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; + ASTERISK_OPENERS_extraword_mod3_0.head = -1; + ASTERISK_OPENERS_extraword_mod3_0.tail = -1; + ASTERISK_OPENERS_extraword_mod3_1.head = -1; + ASTERISK_OPENERS_extraword_mod3_1.tail = -1; + ASTERISK_OPENERS_extraword_mod3_2.head = -1; + ASTERISK_OPENERS_extraword_mod3_2.tail = -1; + ASTERISK_OPENERS_intraword_mod3_0.head = -1; + ASTERISK_OPENERS_intraword_mod3_0.tail = -1; + ASTERISK_OPENERS_intraword_mod3_1.head = -1; + ASTERISK_OPENERS_intraword_mod3_1.tail = -1; + ASTERISK_OPENERS_intraword_mod3_2.head = -1; + ASTERISK_OPENERS_intraword_mod3_2.tail = -1; UNDERSCORE_OPENERS.head = -1; UNDERSCORE_OPENERS.tail = -1; TILDE_OPENERS.head = -1; @@ -4048,14 +4073,24 @@ md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines) break; if(text_type == MD_TEXT_CODE) { - /* Inside code spans, new lines are transformed into single - * spaces. */ + 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)); + /* 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(MD_TEXT_CODE, 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); + } else if(text_type == MD_TEXT_HTML) { /* Inside raw HTML, we output the new line verbatim, including * any trailing spaces. */ @@ -4285,6 +4320,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; @@ -4351,7 +4387,7 @@ 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) SIZEOF_ARRAY(indent_chunk_str)) { MD_TEXT(text_type, indent_chunk_str, indent_chunk_size); indent -= SIZEOF_ARRAY(indent_chunk_str); } @@ -4425,6 +4461,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; } @@ -4715,6 +4753,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)); @@ -4737,10 +4776,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. */ @@ -4809,7 +4868,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; @@ -4820,12 +4879,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; @@ -4864,9 +4927,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++; @@ -4956,11 +5016,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; @@ -5027,7 +5089,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 }; @@ -5047,7 +5109,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; } @@ -5328,6 +5390,9 @@ md_is_container_mark(MD_CTX* ctx, unsigned indent, OFF beg, OFF* p_end, MD_CONTA OFF off = beg; OFF max_end; + if(indent >= ctx->code_indent_offset) + return FALSE; + /* Check for block quote mark. */ if(off < ctx->size && CH(off) == _T('>')) { off++; @@ -5406,6 +5471,7 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end, 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); @@ -5441,356 +5507,365 @@ 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; - - /* 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; - } + 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++; } + } - 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++; + 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; + } } - } - /* 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; + /* 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; - goto done; + line->type = MD_LINE_FENCEDCODE; + break; + } } - } - /* Check whether we are HTML block continuation. */ - if(pivot_line->type == MD_LINE_HTML && ctx->html_block_type > 0) { - int 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; - 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); + 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); - /* Make sure this is the last line of the block. */ - ctx->html_block_type = 0; + /* Make sure this is the last line of the block. */ + ctx->html_block_type = 0; - /* 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; - goto done; + /* 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; + } } - } - - if(n_parents == ctx->n_containers) { - line->type = MD_LINE_HTML; - goto done; - } - } - - /* 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++; - } - 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; + if(n_parents == ctx->n_containers) { + line->type = MD_LINE_HTML; + 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)) - { - 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--; + + /* 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; + 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 > (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 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 > (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--; + } - ctx->last_list_item_starts_with_two_blank_lines = FALSE; + ctx->last_list_item_starts_with_two_blank_lines = FALSE; + } + #endif } -#endif - } - /* 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; + /* 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; - if(md_is_setext_underline(ctx, off, &off, &level)) { - line->type = MD_LINE_SETEXTUNDERLINE; - line->data = level; - goto done; + if(md_is_setext_underline(ctx, off, &off, &level)) { + line->type = MD_LINE_SETEXTUNDERLINE; + line->data = level; + break; + } } - } - /* 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; + /* Check for thematic break line. */ + if(line->indent < ctx->code_indent_offset && ISANYOF(off, _T("-_*")) && off >= hr_killer) { + if(md_is_hr_line(ctx, off, &off, &hr_killer)) { + line->type = MD_LINE_HR; + break; + } } - } - - /* 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)) - { - pivot_line = &md_dummy_blank_line; + /* 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; - off = tmp; + if(md_is_container_mark(ctx, line->indent, off, &tmp, &container) && + md_is_container_compatible(&ctx->containers[n_parents], &container)) + { + 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; + off = tmp; - /* 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--; - } + 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--; + } - ctx->containers[n_parents].mark_indent = container.mark_indent; - ctx->containers[n_parents].contents_indent = container.contents_indent; + ctx->containers[n_parents].mark_indent = container.mark_indent; + ctx->containers[n_parents].contents_indent = container.contents_indent; - n_brothers++; - goto redo; + n_brothers++; + continue; + } } - } - - /* 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 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))) + /* 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)) { - /* 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) - { - /* 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->type = MD_LINE_INDENTEDCODE; + MD_ASSERT(line->indent >= ctx->code_indent_offset); + line->indent -= ctx->code_indent_offset; + line->data = 0; + break; + } - 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; + /* 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) + { + /* Noop. Ordered list cannot interrupt a paragraph unless the start index is 1. */ } else { - container.contents_indent += 1; - line->indent--; - } + 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--; + } - if(n_brothers + n_children == 0) - pivot_line = &md_dummy_blank_line; + 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)); + 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; + n_children++; + MD_CHECK(md_push_container(ctx, &container)); + continue; + } } - } - /* Check whether we are table continuation. */ - if(pivot_line->type == MD_LINE_TABLE && md_is_table_row(ctx, off, &off) && - n_parents == ctx->n_containers) - { - line->type = MD_LINE_TABLE; - goto done; - } + /* Check whether we are table continuation. */ + if(pivot_line->type == MD_LINE_TABLE && md_is_table_row(ctx, off, &off) && + n_parents == ctx->n_containers) + { + line->type = MD_LINE_TABLE; + break; + } - /* Check for ATX header. */ - if(line->indent < ctx->code_indent_offset && CH(off) == _T('#')) { - unsigned level; + /* 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; + if(md_is_atxheader_line(ctx, off, &line->beg, &off, &level)) { + line->type = MD_LINE_ATXHEADER; + line->data = level; + 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 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; + break; + } } - } - /* Check for start of raw HTML block. */ - if(CH(off) == _T('<') && !(ctx->parser.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; + /* Check for start of raw HTML block. */ + if(CH(off) == _T('<') && !(ctx->parser.flags & MD_FLAG_NOHTMLBLOCKS)) + { + ctx->html_block_type = md_is_html_block_start_condition(ctx, off); - 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. */ + /* HTML block type 7 cannot interrupt paragraph. */ + if(ctx->html_block_type == 7 && pivot_line->type == MD_LINE_TEXT) ctx->html_block_type = 0; - } - line->type = MD_LINE_HTML; - goto done; - } - } + 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; + } - /* Check for table underline. */ - if((ctx->parser.flags & MD_FLAG_TABLES) && pivot_line->type == MD_LINE_TEXT && - (CH(off) == _T('|') || CH(off) == _T('-') || CH(off) == _T(':')) && - n_parents == ctx->n_containers) - { - unsigned col_count; + line->type = MD_LINE_HTML; + break; + } + } - 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 table underline. */ + if((ctx->parser.flags & MD_FLAG_TABLES) && pivot_line->type == MD_LINE_TEXT && + (CH(off) == _T('|') || CH(off) == _T('-') || CH(off) == _T(':')) && + n_parents == ctx->n_containers) { - line->data = col_count; - line->type = MD_LINE_TABLEUNDERLINE; - goto done; - } - } + 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)) + { + line->data = col_count; + line->type = MD_LINE_TABLEUNDERLINE; + break; + } + } - /* 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("-+*.)"))) - { - OFF tmp = 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; + } - 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))) + /* 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("-+*.)"))) { - 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(ISWHITESPACE(off)) - off++; - line->beg = off; + 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(ISWHITESPACE(off)) + off++; + line->beg = off; + } } + + 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; @@ -5855,7 +5930,7 @@ abort: } 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; @@ -5884,8 +5959,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; } @@ -6000,9 +6084,10 @@ md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userd ctx.userdata = userdata; ctx.code_indent_offset = (ctx.parser.flags & MD_FLAG_NOINDENTEDCODEBLOCKS) ? (OFF)(-1) : 4; md_build_mark_char_map(&ctx); + ctx.doc_ends_with_newline = (size > 0 && ISNEWLINE_(text[size-1])); /* 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/src/3rdparty/md4c/md4c.h b/src/3rdparty/md4c/md4c.h index 10cba67e95..dcdadad88d 100644 --- a/src/3rdparty/md4c/md4c.h +++ b/src/3rdparty/md4c/md4c.h @@ -30,10 +30,12 @@ extern "C" { #endif -/* Magic to support UTF-16. */ #if defined MD4C_USE_UTF16 + /* Magic to support UTF-16. Not 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 supported on Windows. @@ -236,6 +238,7 @@ 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_TH and MD_BLOCK_TD. */ diff --git a/src/3rdparty/md4c/qt_attribution.json b/src/3rdparty/md4c/qt_attribution.json index 9180ed69b5..fa0fd18853 100644 --- a/src/3rdparty/md4c/qt_attribution.json +++ b/src/3rdparty/md4c/qt_attribution.json @@ -9,7 +9,7 @@ "License": "MIT License", "LicenseId": "MIT", "LicenseFile": "LICENSE.md", - "Version": "0.3.0", - "DownloadLocation": "https://github.com/mity/md4c/releases/tag/release-0.3.0-rc", + "Version": "0.3.3", + "DownloadLocation": "https://github.com/mity/md4c/releases/tag/release-0.3.3", "Copyright": "Copyright © 2016-2019 Martin Mitáš" } |