Commit 63a92c08628c89346be521b5de8cc0685cfd4b2f

Martin Mitas 2016-10-04T00:18:08

Implement thematic breaks (<hr>).

diff --git a/README.md b/README.md
index 5b8c15b..559f8f4 100644
--- a/README.md
+++ b/README.md
@@ -81,7 +81,7 @@ more or less forms our to do list.
   - [ ] 3.2 Container blocks and leaf blocks
 
 - **Leaf Blocks:**
-  - [ ] 4.1 Thematic breaks
+  - [x] 4.1 Thematic breaks
   - [ ] 4.2 ATX headings
   - [ ] 4.3 Setext headings
   - [ ] 4.4 Indented code blocks
diff --git a/md2html/md2html.c b/md2html/md2html.c
index 5a631e0..c5718dd 100644
--- a/md2html/md2html.c
+++ b/md2html/md2html.c
@@ -132,6 +132,7 @@ enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
 
     switch(type) {
     case MD_BLOCK_DOC:      /* noop */ break;
+    case MD_BLOCK_HR:       MEMBUF_APPEND_LITERAL(out, "<hr>\n"); break;
     case MD_BLOCK_P:        MEMBUF_APPEND_LITERAL(out, "<p>"); break;
     }
 
@@ -145,6 +146,7 @@ leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
 
     switch(type) {
     case MD_BLOCK_DOC:      /*noop*/ break;
+    case MD_BLOCK_HR:       /*noop*/ break;
     case MD_BLOCK_P:        MEMBUF_APPEND_LITERAL(out, "</p>\n"); break;
     }
 
diff --git a/md4c/md4c.c b/md4c/md4c.c
index 906f36e..ef741fd 100644
--- a/md4c/md4c.c
+++ b/md4c/md4c.c
@@ -72,6 +72,7 @@ struct MD_CTX_tag {
 typedef enum MD_LINETYPE_tag MD_LINETYPE;
 enum MD_LINETYPE_tag {
     MD_LINE_BLANK,
+    MD_LINE_HR,
     MD_LINE_TEXT
 };
 
@@ -148,6 +149,7 @@ md_log(MD_CTX* ctx, const char* fmt, ...)
 #define ISDIGIT_(ch)            (_T('0') <= (ch) && (ch) <= _T('9'))
 #define ISXDIGIT_(ch)           (ISDIGIT_(ch) || (_T('a') < (ch) && (ch) <= _T('f') || (_T('A') < (ch) && (ch) <= _T('F'))
 #define ISALNUM_(ch)            (ISALPHA_(ch) || ISDIGIT_(ch))
+#define ISANYOF_(ch, palette)   (md_strchr((palette), (ch)) != NULL)
 
 #define ISASCII(off)            ISASCII_(CH(off))
 #define ISBLANK(off)            ISBLANK_(CH(off))
@@ -161,6 +163,19 @@ md_log(MD_CTX* ctx, const char* fmt, ...)
 #define ISDIGIT(off)            ISDIGIT_(CH(off))
 #define ISXDIGIT(off)           ISXDIGIT_(CH(off))
 #define ISALNUM(off)            ISALNUM_(CH(off))
+#define ISANYOF(off, palette)   ISANYOF_(CH(off), (palette))
+
+
+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;
+}
 
 
 #define MD_ENTER_BLOCK(type, arg)                                       \
@@ -235,6 +250,25 @@ abort:
  ***  Breaking Document into Blocks  ***
  ***************************************/
 
+static int
+md_is_hr_line(MD_CTX* ctx, OFF beg, OFF* p_end)
+{
+    OFF off = beg + 1;
+    int n = 1;
+
+    while(off < ctx->size  &&  (CH(off) == CH(beg) || CH(off) == _T(' '))) {
+        if(CH(off) == CH(beg))
+            n++;
+        off++;
+    }
+
+    if(n < 3)
+        return -1;
+
+    *p_end = off;
+    return 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. */
 static void
@@ -258,6 +292,14 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end, const MD_LINE* pivot_line, MD_
         goto done;
     }
 
+    /* Check whether we are thematic break line. */
+    if(ISANYOF(off, _T("-_*"))) {
+        if(md_is_hr_line(ctx, off, &off) == 0) {
+            line->type = MD_LINE_HR;
+            goto done;
+        }
+    }
+
     /* By default, we are normal text line. */
     line->type = MD_LINE_TEXT;
 
@@ -293,17 +335,17 @@ md_process_block(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
 
     /* Derive block type from type of the first line. */
     switch(lines[0].type) {
-    case MD_LINE_BLANK:
-        return 0;
-
-    case MD_LINE_TEXT:
-        block_type = MD_BLOCK_P;
-        break;
+    case MD_LINE_BLANK:     return 0;
+    case MD_LINE_HR:        block_type = MD_BLOCK_HR; break;
+    case MD_LINE_TEXT:      block_type = MD_BLOCK_P; break;
     }
 
     /* Process the block accordingly to is type. */
     MD_ENTER_BLOCK(block_type, NULL);
-    ret = md_process_normal_block(ctx, lines, n_lines);
+    switch(block_type) {
+    case MD_BLOCK_HR:   /* Noop. */ break;
+    default:            ret = md_process_normal_block(ctx, lines, n_lines); break;
+    }
     if(ret != 0)
         goto abort;
     MD_LEAVE_BLOCK(block_type, NULL);
@@ -350,6 +392,10 @@ md_process_doc(MD_CTX *ctx)
 
         /* The same block continues as long lines are of the same type. */
         if(line->type == pivot_line->type) {
+            /* But not so thematic break. */
+            if(line->type == MD_LINE_HR)
+                goto force_block_end;
+
             /* Do not grow the 'lines' because of blank lines. Semantically
              * one blank line is equivalent to many. */
             if(line->type != MD_LINE_BLANK)
@@ -358,6 +404,7 @@ md_process_doc(MD_CTX *ctx)
             continue;
         }
 
+force_block_end:
         /* Otherwise the old block is complete and we have to process it. */
         ret = md_process_block(ctx, lines, n_lines);
         if(ret != 0)
diff --git a/md4c/md4c.h b/md4c/md4c.h
index 0715046..3065c28 100644
--- a/md4c/md4c.h
+++ b/md4c/md4c.h
@@ -61,6 +61,9 @@ enum MD_BLOCKTYPE_tag {
     /* <body>...</body> */
     MD_BLOCK_DOC = 0,
 
+    /* <hr> */
+    MD_BLOCK_HR,
+
     /* <p>...</p> */
     MD_BLOCK_P
 };