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