Commit ba504fda7412a232242004ca56d266094222900a

Martin Mitas 2016-10-04T02:27:43

Implement Setext headers.

diff --git a/README.md b/README.md
index ad74f0f..c1232bf 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,7 @@ more or less forms our to do list.
 - **Leaf Blocks:**
   - [x] 4.1 Thematic breaks
   - [x] 4.2 ATX headings
-  - [ ] 4.3 Setext headings
+  - [x] 4.3 Setext headings
   - [ ] 4.4 Indented code blocks
   - [ ] 4.5 Fenced code blocks
   - [ ] 4.6 HTML blocks
diff --git a/md4c/md4c.c b/md4c/md4c.c
index e439735..9eaa4a6 100644
--- a/md4c/md4c.c
+++ b/md4c/md4c.c
@@ -85,6 +85,8 @@ enum MD_LINETYPE_tag {
     MD_LINE_BLANK,
     MD_LINE_HR,
     MD_LINE_ATXHEADER,
+    MD_LINE_SETEXTHEADER,
+    MD_LINE_SETEXTUNDERLINE,
     MD_LINE_TEXT
 };
 
@@ -304,6 +306,29 @@ md_is_atxheader_line(MD_CTX* ctx, OFF beg, OFF* p_beg, OFF* p_end)
     return 0;
 }
 
+static int
+md_is_setext_underline(MD_CTX* ctx, OFF beg, OFF* p_end)
+{
+    OFF off = beg + 1;
+
+    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++;
+
+    /* But nothing more is allowed on the line. */
+    if(off < ctx->size  &&  !ISNEWLINE(off))
+        return -1;
+
+    ctx->header_level = (CH(beg) == _T('=') ? 1 : 2);
+    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
@@ -335,6 +360,14 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end, const MD_LINE* pivot_line, MD_
         }
     }
 
+    /* Check whether we are setext underline. */
+    if(pivot_line->type == MD_LINE_TEXT  &&  (CH(off) == _T('=') || CH(off) == _T('-'))) {
+        if(md_is_setext_underline(ctx, off, &off) == 0) {
+            line->type = MD_LINE_SETEXTUNDERLINE;
+            goto done;
+        }
+    }
+
     /* Check whether we are thematic break line. */
     if(ISANYOF(off, _T("-_*"))) {
         if(md_is_hr_line(ctx, off, &off) == 0) {
@@ -403,6 +436,7 @@ md_process_block(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
             break;
 
         case MD_LINE_ATXHEADER:
+        case MD_LINE_SETEXTHEADER:
             block_type = MD_BLOCK_H;
             det.header.level = ctx->header_level;
             break;
@@ -410,6 +444,11 @@ md_process_block(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
         case MD_LINE_TEXT:
             block_type = MD_BLOCK_P;
             break;
+
+        case MD_LINE_SETEXTUNDERLINE:
+        default:
+            MD_UNREACHABLE();
+            break;
     }
 
     MD_ENTER_BLOCK(block_type, (void*) &det);
@@ -486,6 +525,13 @@ md_process_doc(MD_CTX *ctx)
             continue;
         }
 
+        /* MD_LINE_SETEXTUNDERLINE changes meaning of the previous block. */
+        if(line->type == MD_LINE_SETEXTUNDERLINE) {
+            MD_ASSERT(n_lines > 0);
+            lines[0].type = MD_LINE_SETEXTHEADER;
+            line->type = MD_LINE_BLANK;
+        }
+
         /* New block also starts if line type changes. */
         if(line->type != pivot_line->type) {
             ret = md_process_block(ctx, lines, n_lines);