packages feed

cmark 0.5.1 → 0.5.2

raw patch · 17 files changed

+515/−287 lines, 17 files

Files

README.md view
@@ -3,8 +3,8 @@  This package provides Haskell bindings for [libcmark], the reference parser for [CommonMark], a fully specified variant of Markdown.-It includes sources for [libcmark] (version 0.23.0, implementing-version 0.23 of the spec) and does not require prior installation of+It includes sources for [libcmark] (version 0.25.2, implementing+version 0.25 of the spec) and does not require prior installation of the C library.  cmark provides the following advantages over existing Markdown
cbits/blocks.c view
@@ -1,3 +1,10 @@+/**+ * Block parsing implementation.+ *+ * For a high-level overview of the block parsing process,+ * see http://spec.commonmark.org/0.24/#phase-1-block-structure+ */+ #include <stdlib.h> #include <assert.h> #include <stdio.h>@@ -17,12 +24,20 @@ #define CODE_INDENT 4 #define TAB_STOP 4 +#ifndef MIN+#define MIN(x, y) ((x < y) ? x : y)+#endif+ #define peek_at(i, n) (i)->data[n] -static inline bool S_is_line_end_char(char c) {+static CMARK_INLINE bool S_is_line_end_char(char c) {   return (c == '\n' || c == '\r'); } +static CMARK_INLINE bool S_is_space_or_tab(char c) {+  return (c == ' ' || c == '\t');+}+ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,                           size_t len, bool eof); @@ -70,10 +85,12 @@   parser->first_nonspace_column = 0;   parser->indent = 0;   parser->blank = false;+  parser->partially_consumed_tab = false;   parser->curline = line;   parser->last_line_length = 0;   parser->linebuf = buf;   parser->options = options;+  parser->last_buffer_ended_with_cr = false;    return parser; }@@ -110,7 +127,7 @@   return true; } -static inline bool can_contain(cmark_node_type parent_type,+static CMARK_INLINE bool can_contain(cmark_node_type parent_type,                                cmark_node_type child_type) {   return (parent_type == CMARK_NODE_DOCUMENT ||           parent_type == CMARK_NODE_BLOCK_QUOTE ||@@ -118,15 +135,32 @@           (parent_type == CMARK_NODE_LIST && child_type == CMARK_NODE_ITEM)); } -static inline bool accepts_lines(cmark_node_type block_type) {+static CMARK_INLINE bool accepts_lines(cmark_node_type block_type) {   return (block_type == CMARK_NODE_PARAGRAPH ||           block_type == CMARK_NODE_HEADING ||           block_type == CMARK_NODE_CODE_BLOCK); } -static void add_line(cmark_node *node, cmark_chunk *ch, bufsize_t offset) {+static CMARK_INLINE bool contains_inlines(cmark_node_type block_type) {+  return (block_type == CMARK_NODE_PARAGRAPH ||+          block_type == CMARK_NODE_HEADING);+}++static void add_line(cmark_node *node, cmark_chunk *ch, cmark_parser *parser) {+  int chars_to_tab;+  int i;   assert(node->open);-  cmark_strbuf_put(&node->string_content, ch->data + offset, ch->len - offset);+  if (parser->partially_consumed_tab) {+    parser->offset += 1; // skip over tab+    // add space characters:+    chars_to_tab = TAB_STOP - (parser->column % TAB_STOP);+    for (i = 0; i < chars_to_tab; i++) {+      cmark_strbuf_putc(&node->string_content, ' ');+    }+  }+  cmark_strbuf_put(&node->string_content,+		  ch->data + parser->offset,+		  ch->len - parser->offset); }  static void remove_trailing_blank_lines(cmark_strbuf *ln) {@@ -336,8 +370,7 @@   while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {     cur = cmark_iter_get_node(iter);     if (ev_type == CMARK_EVENT_ENTER) {-      if (cur->type == CMARK_NODE_PARAGRAPH ||-          cur->type == CMARK_NODE_HEADING) {+      if (contains_inlines(cur->type)) {         cmark_parse_inlines(cur, refmap, options);       }     }@@ -474,6 +507,11 @@   const unsigned char *end = buffer + len;   static const uint8_t repl[] = {239, 191, 189}; +  if (parser->last_buffer_ended_with_cr && *buffer == '\n') {+    // skip NL if last buffer ended with CR ; see #117+    buffer++;+  }+  parser->last_buffer_ended_with_cr = false;   while (buffer < end) {     const unsigned char *eol;     bufsize_t chunk_len;@@ -514,8 +552,11 @@      buffer += chunk_len;     // skip over line ending characters:-    if (buffer < end && *buffer == '\r')+    if (buffer < end && *buffer == '\r') {       buffer++;+      if (buffer == end)+	parser->last_buffer_ended_with_cr = true;+    }     if (buffer < end && *buffer == '\n')       buffer++;   }@@ -532,13 +573,15 @@     n--;    // Check for a space before the final #s:-  if (n != orig_n && n >= 0 &&-      (peek_at(ch, n) == ' ' || peek_at(ch, n) == '\t')) {+  if (n != orig_n && n >= 0 && S_is_space_or_tab(peek_at(ch, n))) {     ch->len = n;     cmark_chunk_rtrim(ch);   } } +// Find first nonspace character from current offset, setting+// parser->first_nonspace, parser->first_nonspace_column,+// parser->indent, and parser->blank. Does not advance parser->offset. static void S_find_first_nonspace(cmark_parser *parser, cmark_chunk *input) {   char c;   int chars_to_tab = TAB_STOP - (parser->column % TAB_STOP);@@ -566,17 +609,37 @@   parser->blank = S_is_line_end_char(peek_at(input, parser->first_nonspace)); } +// Advance parser->offset and parser->column.  parser->offset is the+// byte position in input; parser->column is a virtual column number+// that takes into account tabs. (Multibyte characters are not taken+// into account, because the Markdown line prefixes we are interested in+// analyzing are entirely ASCII.)  The count parameter indicates+// how far to advance the offset.  If columns is true, then count+// indicates a number of columns; otherwise, a number of bytes.+// If advancing a certain number of columns partially consumes+// a tab character, parser->partially_consumed_tab is set to true. static void S_advance_offset(cmark_parser *parser, cmark_chunk *input,                              bufsize_t count, bool columns) {   char c;   int chars_to_tab;+  int chars_to_advance;   while (count > 0 && (c = peek_at(input, parser->offset))) {     if (c == '\t') {       chars_to_tab = TAB_STOP - (parser->column % TAB_STOP);-      parser->column += chars_to_tab;-      parser->offset += 1;-      count -= (columns ? chars_to_tab : 1);+      if (columns) {+	parser->partially_consumed_tab = chars_to_tab > count;+	chars_to_advance = MIN(count, chars_to_tab);+	parser->column += chars_to_advance;+        parser->offset += (parser->partially_consumed_tab ? 0 : 1);+	count -= chars_to_advance;+      } else {+	parser->partially_consumed_tab = false;+	parser->column += chars_to_tab;+	parser->offset += 1;+	count -= 1;+      }     } else {+      parser->partially_consumed_tab = false;       parser->offset += 1;       parser->column += 1; // assume ascii; block starts are ascii       count -= 1;@@ -584,263 +647,314 @@   } } -static void S_process_line(cmark_parser *parser, const unsigned char *buffer,-                           bufsize_t bytes) {-  cmark_node *last_matched_container;+static bool S_last_child_is_open(cmark_node *container) {+  return container->last_child && container->last_child->open;+}++static bool parse_block_quote_prefix(cmark_parser *parser,+                                cmark_chunk *input)+{+  bool res = false;   bufsize_t matched = 0;-  int lev = 0;-  int i;-  cmark_list *data = NULL;-  bool all_matched = true;-  cmark_node *container;-  bool indented;-  cmark_chunk input;-  bool maybe_lazy; -  if (parser->options & CMARK_OPT_VALIDATE_UTF8) {-    cmark_utf8proc_check(parser->curline, buffer, bytes);-  } else {-    cmark_strbuf_put(parser->curline, buffer, bytes);-  }-  // ensure line ends with a newline:-  if (bytes == 0 || !S_is_line_end_char(parser->curline->ptr[bytes - 1])) {-    cmark_strbuf_putc(parser->curline, '\n');+  matched =+      parser->indent <= 3 && peek_at(input, parser->first_nonspace) == '>';+  if (matched) {++    S_advance_offset(parser, input, parser->indent + 1, true);++    if (S_is_space_or_tab(peek_at(input, parser->offset))) {+      S_advance_offset(parser, input, 1, true);+    }++    res = true;   }-  parser->offset = 0;-  parser->column = 0;-  parser->blank = false;+  return res;+} -  input.data = parser->curline->ptr;-  input.len = parser->curline->size;+static bool parse_node_item_prefix(cmark_parser *parser,+                              cmark_chunk *input,+                              cmark_node *container)+{+  bool res = false; -  // container starts at the document root.-  container = parser->root;+  if (parser->indent >=+      container->as.list.marker_offset + container->as.list.padding) {+    S_advance_offset(parser, input, container->as.list.marker_offset ++                                         container->as.list.padding,+                     true);+    res = true;+  } else if (parser->blank && container->first_child != NULL) {+    // if container->first_child is NULL, then the opening line+    // of the list item was blank after the list marker; in this+    // case, we are done with the list item.+    S_advance_offset(parser, input,+                     parser->first_nonspace - parser->offset, false);+    res = true;+  }+  return res;+} -  parser->line_number++;+static bool parse_code_block_prefix(cmark_parser *parser,+                               cmark_chunk *input,+                               cmark_node *container,+                               bool *should_continue)+{+  bool res = false; -  // for each containing node, try to parse the associated line start.-  // bail out on failure:  container will point to the last matching node.+  if (!container->as.code.fenced) { // indented+    if (parser->indent >= CODE_INDENT) {+      S_advance_offset(parser, input, CODE_INDENT, true);+      res = true;+    } else if (parser->blank) {+      S_advance_offset(parser, input,+                       parser->first_nonspace - parser->offset, false);+      res = true;+    }+  } else { // fenced+    bufsize_t matched = 0; -  while (container->last_child && container->last_child->open) {-    container = container->last_child;+    if (parser->indent <= 3 && (peek_at(input, parser->first_nonspace) ==+                                container->as.code.fence_char)) {+      matched = scan_close_code_fence(input, parser->first_nonspace);+    } -    S_find_first_nonspace(parser, &input);+    if (matched >= container->as.code.fence_length) {+      // closing fence - and since we're at+      // the end of a line, we can stop processing it:+      *should_continue = false;+      S_advance_offset(parser, input, matched, false);+      parser->current = finalize(parser, container);+    } else {+      // skip opt. spaces of fence parser->offset+      int i = container->as.code.fence_offset; -    if (container->type == CMARK_NODE_BLOCK_QUOTE) {-      matched =-          parser->indent <= 3 && peek_at(&input, parser->first_nonspace) == '>';-      if (matched) {-        S_advance_offset(parser, &input, parser->indent + 1, true);-        if (peek_at(&input, parser->offset) == ' ')-          parser->offset++;-      } else {-        all_matched = false;+      while (i > 0 && S_is_space_or_tab(peek_at(input, parser->offset))) {+        S_advance_offset(parser, input, 1, true);+        i--;       }+      res = true;+    }+  } -    } else if (container->type == CMARK_NODE_ITEM) {-      if (parser->indent >=-          container->as.list.marker_offset + container->as.list.padding) {-        S_advance_offset(parser, &input, container->as.list.marker_offset +-                                             container->as.list.padding,-                         true);-      } else if (parser->blank && container->first_child != NULL) {-        // if container->first_child is NULL, then the opening line-        // of the list item was blank after the list marker; in this-        // case, we are done with the list item.-        S_advance_offset(parser, &input,-                         parser->first_nonspace - parser->offset, false);-      } else {-        all_matched = false;-      }+  return res;+} -    } else if (container->type == CMARK_NODE_CODE_BLOCK) {+static bool parse_html_block_prefix(cmark_parser *parser,+                               cmark_node *container)+{+  bool res = false;+  int html_block_type = container->as.html_block_type; -      if (!container->as.code.fenced) { // indented-        if (parser->indent >= CODE_INDENT) {-          S_advance_offset(parser, &input, CODE_INDENT, true);-        } else if (parser->blank) {-          S_advance_offset(parser, &input,-                           parser->first_nonspace - parser->offset, false);-        } else {-          all_matched = false;-        }-      } else { // fenced-        matched = 0;-        if (parser->indent <= 3 && (peek_at(&input, parser->first_nonspace) ==-                                    container->as.code.fence_char)) {-          matched = scan_close_code_fence(&input, parser->first_nonspace);-        }-        if (matched >= container->as.code.fence_length) {-          // closing fence - and since we're at-          // the end of a line, we can return:-          all_matched = false;-          S_advance_offset(parser, &input, matched, false);-          parser->current = finalize(parser, container);-          goto finished;-        } else {-          // skip opt. spaces of fence parser->offset-          i = container->as.code.fence_offset;-          while (i > 0 && peek_at(&input, parser->offset) == ' ') {-            S_advance_offset(parser, &input, 1, false);-            i--;-          }-        }-      }-    } else if (container->type == CMARK_NODE_HEADING) {+  assert(html_block_type >= 1 && html_block_type <= 7);+  switch (html_block_type) {+    case 1:+    case 2:+    case 3:+    case 4:+    case 5:+      // these types of blocks can accept blanks+      res = true;+      break;+    case 6:+    case 7:+      res = !parser->blank;+      break;+  } -      // a heading can never contain more than one line-      all_matched = false;+  return res;+} -    } else if (container->type == CMARK_NODE_HTML_BLOCK) {+/**+ * For each containing node, try to parse the associated line start.+ *+ * Will not close unmatched blocks, as we may have a lazy continuation+ * line -> http://spec.commonmark.org/0.24/#lazy-continuation-line+ *+ * Returns: The last matching node, or NULL+ */+static cmark_node *check_open_blocks(cmark_parser *parser,+                                     cmark_chunk *input,+                                     bool *all_matched)+{+  bool should_continue = true;+  *all_matched = false;+  cmark_node *container = parser->root;+  cmark_node_type cont_type; -      switch (container->as.html_block_type) {-      case 1:-      case 2:-      case 3:-      case 4:-      case 5:-        // these types of blocks can accept blanks+  while (S_last_child_is_open(container)) {+    container = container->last_child;+    cont_type = container->type;++    S_find_first_nonspace(parser, input);++    switch (cont_type) {+      case CMARK_NODE_BLOCK_QUOTE:+        if (!parse_block_quote_prefix(parser, input))+          goto done;         break;-      case 6:-      case 7:-        if (parser->blank) {-          all_matched = false;-        }+      case CMARK_NODE_ITEM:+        if (!parse_node_item_prefix(parser, input, container))+          goto done;         break;+      case CMARK_NODE_CODE_BLOCK:+        if (!parse_code_block_prefix(parser, input, container, &should_continue))+          goto done;+        break;+      case CMARK_NODE_HEADING:+        // a heading can never contain more than one line+        goto done;+      case CMARK_NODE_HTML_BLOCK:+        if (!parse_html_block_prefix(parser, container))+          goto done;+        break;+      case CMARK_NODE_PARAGRAPH:+        if (parser->blank)+          goto done;+        break;       default:-        fprintf(stderr, "Error (%s:%d): Unknown HTML block type %d\n", __FILE__,-                __LINE__, container->as.html_block_type);-        exit(1);-      }+        break;+    }+  } -    } else if (container->type == CMARK_NODE_PARAGRAPH) {+  *all_matched = true; -      if (parser->blank) {-        all_matched = false;-      }-    }+done:+  if (!*all_matched) {+    container = container->parent; // back up to last matching node+  } -    if (!all_matched) {-      container = container->parent; // back up to last matching node-      break;-    }+  if (!should_continue) {+    container = NULL;   } -  last_matched_container = container;+  return container;+} -  // check to see if we've hit 2nd blank line, break out of list:-  if (parser->blank && container->last_line_blank) {-    break_out_of_lists(parser, &container);-  }+static void open_new_blocks(cmark_parser *parser,+                            cmark_node **container,+                            cmark_chunk *input,+                            bool all_matched)+{+  bool indented;+  cmark_list *data = NULL;+  bool maybe_lazy = parser->current->type == CMARK_NODE_PARAGRAPH;+  cmark_node_type cont_type = (*container)->type;+  bufsize_t matched = 0;+  int lev = 0;+  bool save_partially_consumed_tab;+  int save_offset;+  int save_column; -  maybe_lazy = parser->current->type == CMARK_NODE_PARAGRAPH;-  // try new container starts:-  while (container->type != CMARK_NODE_CODE_BLOCK &&-         container->type != CMARK_NODE_HTML_BLOCK) {+  while (cont_type != CMARK_NODE_CODE_BLOCK &&+         cont_type != CMARK_NODE_HTML_BLOCK) { -    S_find_first_nonspace(parser, &input);+    S_find_first_nonspace(parser, input);     indented = parser->indent >= CODE_INDENT; -    if (!indented && peek_at(&input, parser->first_nonspace) == '>') {+    if (!indented && peek_at(input, parser->first_nonspace) == '>') { -      S_advance_offset(parser, &input,+      S_advance_offset(parser, input,                        parser->first_nonspace + 1 - parser->offset, false);       // optional following character-      if (peek_at(&input, parser->offset) == ' ')-        S_advance_offset(parser, &input, 1, false);-      container = add_child(parser, container, CMARK_NODE_BLOCK_QUOTE,+      if (S_is_space_or_tab(peek_at(input, parser->offset))) {+        S_advance_offset(parser, input, 1, true);+      }+      *container = add_child(parser, *container, CMARK_NODE_BLOCK_QUOTE,                             parser->offset + 1);      } else if (!indented && (matched = scan_atx_heading_start(-                                 &input, parser->first_nonspace))) {+                                 input, parser->first_nonspace))) {+      bufsize_t hashpos;+      int level = 0; -      S_advance_offset(parser, &input,+      S_advance_offset(parser, input,                        parser->first_nonspace + matched - parser->offset,                        false);-      container =-          add_child(parser, container, CMARK_NODE_HEADING, parser->offset + 1);+      *container =+          add_child(parser, *container, CMARK_NODE_HEADING, parser->offset + 1); -      bufsize_t hashpos =-          cmark_chunk_strchr(&input, '#', parser->first_nonspace);-      int level = 0;+      hashpos = cmark_chunk_strchr(input, '#', parser->first_nonspace); -      while (peek_at(&input, hashpos) == '#') {+      while (peek_at(input, hashpos) == '#') {         level++;         hashpos++;       }-      container->as.heading.level = level;-      container->as.heading.setext = false; -    } else if (!indented && (matched = scan_open_code_fence(-                                 &input, parser->first_nonspace))) {+      (*container)->as.heading.level = level;+      (*container)->as.heading.setext = false; -      container = add_child(parser, container, CMARK_NODE_CODE_BLOCK,+    } else if (!indented && (matched = scan_open_code_fence(+                                 input, parser->first_nonspace))) {+      *container = add_child(parser, *container, CMARK_NODE_CODE_BLOCK,                             parser->first_nonspace + 1);-      container->as.code.fenced = true;-      container->as.code.fence_char = peek_at(&input, parser->first_nonspace);-      container->as.code.fence_length = matched;-      container->as.code.fence_offset =+      (*container)->as.code.fenced = true;+      (*container)->as.code.fence_char = peek_at(input, parser->first_nonspace);+      (*container)->as.code.fence_length = matched;+      (*container)->as.code.fence_offset =           (int8_t)(parser->first_nonspace - parser->offset);-      container->as.code.info = cmark_chunk_literal("");-      S_advance_offset(parser, &input,+      (*container)->as.code.info = cmark_chunk_literal("");+      S_advance_offset(parser, input,                        parser->first_nonspace + matched - parser->offset,                        false);      } else if (!indented && ((matched = scan_html_block_start(-                                  &input, parser->first_nonspace)) ||-                             (container->type != CMARK_NODE_PARAGRAPH &&+                                  input, parser->first_nonspace)) ||+                             (cont_type != CMARK_NODE_PARAGRAPH &&                               (matched = scan_html_block_start_7(-                                   &input, parser->first_nonspace))))) {--      container = add_child(parser, container, CMARK_NODE_HTML_BLOCK,+                                   input, parser->first_nonspace))))) {+      *container = add_child(parser, *container, CMARK_NODE_HTML_BLOCK,                             parser->first_nonspace + 1);-      container->as.html_block_type = matched;+      (*container)->as.html_block_type = matched;       // note, we don't adjust parser->offset because the tag is part of the       // text--    } else if (!indented && container->type == CMARK_NODE_PARAGRAPH &&+    } else if (!indented && cont_type == CMARK_NODE_PARAGRAPH &&                (lev =-                    scan_setext_heading_line(&input, parser->first_nonspace))) {--      container->type = CMARK_NODE_HEADING;-      container->as.heading.level = lev;-      container->as.heading.setext = true;-      S_advance_offset(parser, &input, input.len - 1 - parser->offset, false);-+                    scan_setext_heading_line(input, parser->first_nonspace))) {+      (*container)->type = CMARK_NODE_HEADING;+      (*container)->as.heading.level = lev;+      (*container)->as.heading.setext = true;+      S_advance_offset(parser, input, input->len - 1 - parser->offset, false);     } else if (!indented &&-               !(container->type == CMARK_NODE_PARAGRAPH && !all_matched) &&+               !(cont_type == CMARK_NODE_PARAGRAPH && !all_matched) &&                (matched =-                    scan_thematic_break(&input, parser->first_nonspace))) {-+                    scan_thematic_break(input, parser->first_nonspace))) {       // it's only now that we know the line is not part of a setext heading:-      container = add_child(parser, container, CMARK_NODE_THEMATIC_BREAK,+      *container = add_child(parser, *container, CMARK_NODE_THEMATIC_BREAK,                             parser->first_nonspace + 1);-      S_advance_offset(parser, &input, input.len - 1 - parser->offset, false);-+      S_advance_offset(parser, input, input->len - 1 - parser->offset, false);     } else if ((matched =-                    parse_list_marker(&input, parser->first_nonspace, &data)) &&-               (!indented || container->type == CMARK_NODE_LIST)) {+                    parse_list_marker(input, parser->first_nonspace, &data)) &&+               (!indented || cont_type == CMARK_NODE_LIST)) {       // Note that we can have new list items starting with >= 4       // spaces indent, as long as the list container is still open.+      int i = 0;        // compute padding:-      S_advance_offset(parser, &input,+      S_advance_offset(parser, input,                        parser->first_nonspace + matched - parser->offset,                        false);-      i = 0;-      while (i <= 5 && peek_at(&input, parser->offset + i) == ' ') {-        i++;++      save_partially_consumed_tab = parser->partially_consumed_tab;+      save_offset = parser->offset;+      save_column = parser->column;++      while (parser->column - save_column <= 5 &&+		S_is_space_or_tab(peek_at(input, parser->offset))) {+        S_advance_offset(parser, input, 1, true);       }-      // i = number of spaces after marker, up to 5-      if (i >= 5 || i < 1 ||-          S_is_line_end_char(peek_at(&input, parser->offset))) {++      i = parser->column - save_column;+      if (i >= 5 || i < 1) {         data->padding = matched + 1;+	parser->offset = save_offset;+	parser->column = save_column;+	parser->partially_consumed_tab = save_partially_consumed_tab;         if (i > 0) {-          S_advance_offset(parser, &input, 1, false);+          S_advance_offset(parser, input, 1, true);         }       } else {         data->padding = matched + i;-        S_advance_offset(parser, &input, i, true);       }        // check container; if it's a list, see if this list item@@ -848,50 +962,57 @@        data->marker_offset = parser->indent; -      if (container->type != CMARK_NODE_LIST ||-          !lists_match(&container->as.list, data)) {-        container = add_child(parser, container, CMARK_NODE_LIST,+      if (cont_type != CMARK_NODE_LIST ||+          !lists_match(&((*container)->as.list), data)) {+        *container = add_child(parser, *container, CMARK_NODE_LIST,                               parser->first_nonspace + 1); -        memcpy(&container->as.list, data, sizeof(*data));+        memcpy(&((*container)->as.list), data, sizeof(*data));       }        // add the list item-      container = add_child(parser, container, CMARK_NODE_ITEM,+      *container = add_child(parser, *container, CMARK_NODE_ITEM,                             parser->first_nonspace + 1);       /* TODO: static */-      memcpy(&container->as.list, data, sizeof(*data));+      memcpy(&((*container)->as.list), data, sizeof(*data));       free(data);-     } else if (indented && !maybe_lazy && !parser->blank) {-      S_advance_offset(parser, &input, CODE_INDENT, true);-      container = add_child(parser, container, CMARK_NODE_CODE_BLOCK,+      S_advance_offset(parser, input, CODE_INDENT, true);+      *container = add_child(parser, *container, CMARK_NODE_CODE_BLOCK,                             parser->offset + 1);-      container->as.code.fenced = false;-      container->as.code.fence_char = 0;-      container->as.code.fence_length = 0;-      container->as.code.fence_offset = 0;-      container->as.code.info = cmark_chunk_literal("");+      (*container)->as.code.fenced = false;+      (*container)->as.code.fence_char = 0;+      (*container)->as.code.fence_length = 0;+      (*container)->as.code.fence_offset = 0;+      (*container)->as.code.info = cmark_chunk_literal("");      } else {       break;     } -    if (accepts_lines(container->type)) {+    if (accepts_lines((*container)->type)) {       // if it's a line container, it can't contain other containers       break;     }++    cont_type = (*container)->type;     maybe_lazy = false;   }+} +static void add_text_to_container (cmark_parser *parser,+                                   cmark_node *container,+                                   cmark_node *last_matched_container,+                                   cmark_chunk *input)+{+  cmark_node *tmp;   // what remains at parser->offset is a text line.  add the text to the   // appropriate container. -  S_find_first_nonspace(parser, &input);+  S_find_first_nonspace(parser, input); -  if (parser->blank && container->last_child) {+  if (parser->blank && container->last_child)     container->last_child->last_line_blank = true;-  }    // block quote lines are never blank as they start with >   // and we don't count blanks in fenced code for purposes of tight/loose@@ -906,61 +1027,61 @@        !(container->type == CMARK_NODE_ITEM && container->first_child == NULL &&          container->start_line == parser->line_number)); -  cmark_node *cont = container;-  while (cont->parent) {-    cont->parent->last_line_blank = false;-    cont = cont->parent;+  tmp = container;+  while (tmp->parent) {+    tmp->parent->last_line_blank = false;+    tmp = tmp->parent;   } +  // If the last line processed belonged to a paragraph node,+  // and we didn't match all of the line prefixes for the open containers,+  // and we didn't start any new containers,+  // and the line isn't blank,+  // then treat this as a "lazy continuation line" and add it to+  // the open paragraph.   if (parser->current != last_matched_container &&-      container == last_matched_container && !parser->blank &&-      parser->current->type == CMARK_NODE_PARAGRAPH &&-      cmark_strbuf_len(&parser->current->string_content) > 0) {--    add_line(parser->current, &input, parser->offset);-+      container == last_matched_container &&+      !parser->blank &&+      parser->current->type == CMARK_NODE_PARAGRAPH) {+    add_line(parser->current, input, parser);   } else { // not a lazy continuation--    // finalize any blocks that were not matched and set cur to container:+    // Finalize any blocks that were not matched and set cur to container:     while (parser->current != last_matched_container) {       parser->current = finalize(parser, parser->current);       assert(parser->current != NULL);     }      if (container->type == CMARK_NODE_CODE_BLOCK) {--      add_line(container, &input, parser->offset);-+      add_line(container, input, parser);     } else if (container->type == CMARK_NODE_HTML_BLOCK) {--      add_line(container, &input, parser->offset);+      add_line(container, input, parser);        int matches_end_condition;       switch (container->as.html_block_type) {       case 1:         // </script>, </style>, </pre>         matches_end_condition =-            scan_html_block_end_1(&input, parser->first_nonspace);+            scan_html_block_end_1(input, parser->first_nonspace);         break;       case 2:         // -->         matches_end_condition =-            scan_html_block_end_2(&input, parser->first_nonspace);+            scan_html_block_end_2(input, parser->first_nonspace);         break;       case 3:         // ?>         matches_end_condition =-            scan_html_block_end_3(&input, parser->first_nonspace);+            scan_html_block_end_3(input, parser->first_nonspace);         break;       case 4:         // >         matches_end_condition =-            scan_html_block_end_4(&input, parser->first_nonspace);+            scan_html_block_end_4(input, parser->first_nonspace);         break;       case 5:         // ]]>         matches_end_condition =-            scan_html_block_end_5(&input, parser->first_nonspace);+            scan_html_block_end_5(input, parser->first_nonspace);         break;       default:         matches_end_condition = 0;@@ -971,28 +1092,69 @@         container = finalize(parser, container);         assert(parser->current != NULL);       }-     } else if (parser->blank) {-       // ??? do nothing-     } else if (accepts_lines(container->type)) {-       if (container->type == CMARK_NODE_HEADING &&           container->as.heading.setext == false) {-        chop_trailing_hashtags(&input);+        chop_trailing_hashtags(input);       }-      add_line(container, &input, parser->first_nonspace);-+      S_advance_offset(parser, input, parser->first_nonspace - parser->offset, false);+      add_line(container, input, parser);     } else {       // create paragraph container for line       container = add_child(parser, container, CMARK_NODE_PARAGRAPH,                             parser->first_nonspace + 1);-      add_line(container, &input, parser->first_nonspace);+      S_advance_offset(parser, input, parser->first_nonspace - parser->offset, false);+      add_line(container, input, parser);     }      parser->current = container;   }+}++/* See http://spec.commonmark.org/0.24/#phase-1-block-structure */+static void S_process_line(cmark_parser *parser, const unsigned char *buffer,+                           bufsize_t bytes) {+  cmark_node *last_matched_container;+  bool all_matched = true;+  cmark_node *container;+  cmark_chunk input;++  if (parser->options & CMARK_OPT_VALIDATE_UTF8)+    cmark_utf8proc_check(parser->curline, buffer, bytes);+  else+    cmark_strbuf_put(parser->curline, buffer, bytes);++  // ensure line ends with a newline:+  if (bytes == 0 || !S_is_line_end_char(parser->curline->ptr[bytes - 1]))+    cmark_strbuf_putc(parser->curline, '\n');++  parser->offset = 0;+  parser->column = 0;+  parser->blank = false;+  parser->partially_consumed_tab = false;++  input.data = parser->curline->ptr;+  input.len = parser->curline->size;++  parser->line_number++;++  last_matched_container = check_open_blocks(parser, &input, &all_matched);++  if (!last_matched_container)+    goto finished;++  container = last_matched_container;++  // check to see if we've hit 2nd blank line, break out of list:+  if (parser->blank && container->last_line_blank)+    break_out_of_lists(parser, &container);++  open_new_blocks(parser, &container, &input, all_matched);++  add_text_to_container(parser, container, last_matched_container, &input);+ finished:   parser->last_line_length = input.len;   if (parser->last_line_length &&
cbits/buffer.c view
@@ -33,7 +33,7 @@   abort(); } -static inline void S_strbuf_grow_by(cmark_strbuf *buf, size_t add) {+static CMARK_INLINE void S_strbuf_grow_by(cmark_strbuf *buf, size_t add) {   size_t target_size = (size_t)buf->size + add;    if (target_size < add            /* Integer overflow. */
cbits/cmark.h view
@@ -20,7 +20,7 @@  */  /** Convert 'text' (assumed to be a UTF-8 encoded string with length- * 'len' from CommonMark Markdown to HTML, returning a null-terminated,+ * 'len') from CommonMark Markdown to HTML, returning a null-terminated,  * UTF-8-encoded string.  */ CMARK_EXPORT@@ -96,7 +96,7 @@  */ CMARK_EXPORT cmark_node *cmark_node_new(cmark_node_type type); -/** Frees the memory allocated for a node.+/** Frees the memory allocated for a node and any children.  */ CMARK_EXPORT void cmark_node_free(cmark_node *node); @@ -418,7 +418,7 @@  * Streaming interface:  *  *     cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT);- *     FILE *fp = fopen("myfile.md", "r");+ *     FILE *fp = fopen("myfile.md", "rb");  *     while ((bytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {  *     	   cmark_parser_feed(parser, buffer, bytes);  *     	   if (bytes < sizeof(buffer)) {
cbits/cmark_version.h view
@@ -1,7 +1,7 @@ #ifndef CMARK_VERSION_H #define CMARK_VERSION_H -#define CMARK_VERSION ((0 << 16) | (23 << 8)  | 0)-#define CMARK_VERSION_STRING "0.23.0"+#define CMARK_VERSION ((0 << 16) | (25 << 8)  | 2)+#define CMARK_VERSION_STRING "0.25.2"  #endif
cbits/commonmark.c view
@@ -17,16 +17,17 @@ #define LIT(s) renderer->out(renderer, s, false, LITERAL) #define CR() renderer->cr(renderer) #define BLANKLINE() renderer->blankline(renderer)+#define ENCODED_SIZE 20+#define LISTMARKER_SIZE 20  // Functions to convert cmark_nodes to commonmark strings. -static inline void outc(cmark_renderer *renderer, cmark_escaping escape,+static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_escaping escape,                         int32_t c, unsigned char nextc) {   bool needs_escaping = false;   bool follows_digit =       renderer->buffer->size > 0 &&       cmark_isdigit(renderer->buffer->ptr[renderer->buffer->size - 1]);-  const size_t ENCODED_SIZE = 20;   char encoded[ENCODED_SIZE];    needs_escaping =@@ -168,7 +169,6 @@   bool entering = (ev_type == CMARK_EVENT_ENTER);   const char *info, *code, *title;   size_t info_len, code_len;-  const size_t LISTMARKER_SIZE = 20;   char listmarker[LISTMARKER_SIZE];   char *emph_delim;   bufsize_t marker_width;@@ -273,8 +273,8 @@     // begin or end with a blank line, and code isn't     // first thing in a list item     if (info_len == 0 &&-        (code_len > 2 && !isspace(code[0]) &&-         !(isspace(code[code_len - 1]) && isspace(code[code_len - 2]))) &&+        (code_len > 2 && !isspace((unsigned char)code[0]) &&+         !(isspace((unsigned char)code[code_len - 1]) && isspace((unsigned char)code[code_len - 2]))) &&         !(node->prev == NULL && node->parent &&           node->parent->type == CMARK_NODE_ITEM)) {       LIT("    ");
cbits/config.h view
@@ -1,3 +1,10 @@+#ifndef CMARK_CONFIG_H+#define CMARK_CONFIG_H++#ifdef __cplusplus+extern "C" {+#endif+ #define HAVE_STDBOOL_H  #ifdef HAVE_STDBOOL_H@@ -22,4 +29,48 @@   #else     #define CMARK_INLINE inline   #endif+#endif++/* snprintf and vsnprintf fallbacks for MSVC before 2015,+   due to Valentin Milea http://stackoverflow.com/questions/2915672/+*/++#if defined(_MSC_VER) && _MSC_VER < 1900++#include <stdio.h>+#include <stdarg.h>++#define snprintf c99_snprintf+#define vsnprintf c99_vsnprintf++CMARK_INLINE int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)+{+    int count = -1;++    if (size != 0)+        count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);+    if (count == -1)+        count = _vscprintf(format, ap);++    return count;+}++CMARK_INLINE int c99_snprintf(char *outBuf, size_t size, const char *format, ...)+{+    int count;+    va_list ap;++    va_start(ap, format);+    count = c99_vsnprintf(outBuf, size, format, ap);+    va_end(ap);++    return count;+}++#endif++#ifdef __cplusplus+}+#endif+ #endif
cbits/html.c view
@@ -10,6 +10,8 @@ #include "houdini.h" #include "scanners.h" +#define BUFFER_SIZE 100+ // Functions to convert cmark_nodes to HTML strings.  static void escape_html(cmark_strbuf *dest, const unsigned char *source,@@ -17,7 +19,7 @@   houdini_escape_html0(dest, source, length, 0); } -static inline void cr(cmark_strbuf *html) {+static CMARK_INLINE void cr(cmark_strbuf *html) {   if (html->size && html->ptr[html->size - 1] != '\n')     cmark_strbuf_putc(html, '\n'); }@@ -29,7 +31,6 @@  static void S_render_sourcepos(cmark_node *node, cmark_strbuf *html,                                int options) {-  const size_t BUFFER_SIZE = 100;   char buffer[BUFFER_SIZE];   if (CMARK_OPT_SOURCEPOS & options) {     snprintf(buffer, BUFFER_SIZE, " data-sourcepos=\"%d:%d-%d:%d\"",@@ -47,7 +48,6 @@   char start_heading[] = "<h0";   char end_heading[] = "</h0";   bool tight;-  const size_t BUFFER_SIZE = 100;   char buffer[BUFFER_SIZE];    bool entering = (ev_type == CMARK_EVENT_ENTER);
cbits/inlines.c view
@@ -48,7 +48,7 @@   delimiter *last_delim; } subject; -static inline bool S_is_line_end_char(char c) {+static CMARK_INLINE bool S_is_line_end_char(char c) {   return (c == '\n' || c == '\r'); } @@ -62,7 +62,7 @@ static bufsize_t subject_find_special_char(subject *subj, int options);  // Create an inline with a literal string value.-static inline cmark_node *make_literal(cmark_node_type t, cmark_chunk s) {+static CMARK_INLINE cmark_node *make_literal(cmark_node_type t, cmark_chunk s) {   cmark_node *e = (cmark_node *)calloc(1, sizeof(*e));   if (e != NULL) {     e->type = t;@@ -81,7 +81,7 @@ }  // Create an inline with no value.-static inline cmark_node *make_simple(cmark_node_type t) {+static CMARK_INLINE cmark_node *make_simple(cmark_node_type t) {   cmark_node *e = (cmark_node *)calloc(1, sizeof(*e));   if (e != NULL) {     e->type = t;@@ -141,7 +141,7 @@   return cmark_chunk_buf_detach(&buf); } -static inline cmark_node *make_autolink(cmark_chunk url, int is_email) {+static CMARK_INLINE cmark_node *make_autolink(cmark_chunk url, int is_email) {   cmark_node *link = make_simple(CMARK_NODE_LINK);   link->as.link.url = cmark_clean_autolink(&url, is_email);   link->as.link.title = cmark_chunk_literal("");@@ -159,28 +159,28 @@   e->last_delim = NULL; } -static inline int isbacktick(int c) { return (c == '`'); }+static CMARK_INLINE int isbacktick(int c) { return (c == '`'); } -static inline unsigned char peek_char(subject *subj) {+static CMARK_INLINE unsigned char peek_char(subject *subj) {   // NULL bytes should have been stripped out by now.  If they're   // present, it's a programming error:   assert(!(subj->pos < subj->input.len && subj->input.data[subj->pos] == 0));   return (subj->pos < subj->input.len) ? subj->input.data[subj->pos] : 0; } -static inline unsigned char peek_at(subject *subj, bufsize_t pos) {+static CMARK_INLINE unsigned char peek_at(subject *subj, bufsize_t pos) {   return subj->input.data[pos]; }  // Return true if there are more characters in the subject.-static inline int is_eof(subject *subj) {+static CMARK_INLINE int is_eof(subject *subj) {   return (subj->pos >= subj->input.len); }  // Advance the subject.  Doesn't check for eof. #define advance(subj) (subj)->pos += 1 -static inline bool skip_spaces(subject *subj) {+static CMARK_INLINE bool skip_spaces(subject *subj) {   bool skipped = false;   while (peek_char(subj) == ' ' || peek_char(subj) == '\t') {     advance(subj);@@ -189,7 +189,7 @@   return skipped; } -static inline bool skip_line_end(subject *subj) {+static CMARK_INLINE bool skip_line_end(subject *subj) {   bool seen_line_end_char = false;   if (peek_char(subj) == '\r') {     advance(subj);@@ -203,7 +203,7 @@ }  // Take characters while a predicate holds, and return a string.-static inline cmark_chunk take_while(subject *subj, int (*f)(int)) {+static CMARK_INLINE cmark_chunk take_while(subject *subj, int (*f)(int)) {   unsigned char c;   bufsize_t startpos = subj->pos;   bufsize_t len = 0;
cbits/latex.c view
@@ -17,8 +17,9 @@ #define LIT(s) renderer->out(renderer, s, false, LITERAL) #define CR() renderer->cr(renderer) #define BLANKLINE() renderer->blankline(renderer)+#define LIST_NUMBER_STRING_SIZE 20 -static inline void outc(cmark_renderer *renderer, cmark_escaping escape,+static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_escaping escape,                         int32_t c, unsigned char nextc) {   if (escape == LITERAL) {     cmark_render_code_point(renderer, c);@@ -217,7 +218,6 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,                          cmark_event_type ev_type, int options) {   int list_number;-  const size_t LIST_NUMBER_STRING_SIZE = 20;   char list_number_string[LIST_NUMBER_STRING_SIZE];   bool entering = (ev_type == CMARK_EVENT_ENTER);   cmark_list_type list_type;
cbits/man.c view
@@ -14,6 +14,7 @@ #define LIT(s) renderer->out(renderer, s, false, LITERAL) #define CR() renderer->cr(renderer) #define BLANKLINE() renderer->blankline(renderer)+#define LIST_NUMBER_SIZE 20  // Functions to convert cmark_nodes to groff man strings. static void S_outc(cmark_renderer *renderer, cmark_escaping escape, int32_t c,@@ -110,7 +111,6 @@           tmp = tmp->prev;           list_number += 1;         }-        const size_t LIST_NUMBER_SIZE = 20;         char list_number_s[LIST_NUMBER_SIZE];         snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number);         LIT(list_number_s);
cbits/node.c view
@@ -6,7 +6,7 @@  static void S_node_unlink(cmark_node *node); -static inline bool S_is_block(cmark_node *node) {+static CMARK_INLINE bool S_is_block(cmark_node *node) {   if (node == NULL) {     return false;   }@@ -14,7 +14,7 @@          node->type <= CMARK_NODE_LAST_BLOCK; } -static inline bool S_is_inline(cmark_node *node) {+static CMARK_INLINE bool S_is_inline(cmark_node *node) {   if (node == NULL) {     return false;   }
cbits/parser.h view
@@ -22,10 +22,12 @@   bufsize_t first_nonspace_column;   int indent;   bool blank;+  bool partially_consumed_tab;   cmark_strbuf *curline;   bufsize_t last_line_length;   cmark_strbuf *linebuf;   int options;+  bool last_buffer_ended_with_cr; };  #ifdef __cplusplus
cbits/render.c view
@@ -5,13 +5,13 @@ #include "utf8.h" #include "render.h" -static inline void S_cr(cmark_renderer *renderer) {+static CMARK_INLINE void S_cr(cmark_renderer *renderer) {   if (renderer->need_cr < 1) {     renderer->need_cr = 1;   } } -static inline void S_blankline(cmark_renderer *renderer) {+static CMARK_INLINE void S_blankline(cmark_renderer *renderer) {   if (renderer->need_cr < 2) {     renderer->need_cr = 2;   }
cbits/xml.c view
@@ -9,6 +9,8 @@ #include "buffer.h" #include "houdini.h" +#define BUFFER_SIZE 100+ // Functions to convert cmark_nodes to XML strings.  static void escape_xml(cmark_strbuf *dest, const unsigned char *source,@@ -21,7 +23,7 @@   int indent; }; -static inline void indent(struct render_state *state) {+static CMARK_INLINE void indent(struct render_state *state) {   int i;   for (i = 0; i < state->indent; i++) {     cmark_strbuf_putc(state->xml, ' ');@@ -34,7 +36,6 @@   bool literal = false;   cmark_delim_type delim;   bool entering = (ev_type == CMARK_EVENT_ENTER);-  const size_t BUFFER_SIZE = 100;   char buffer[BUFFER_SIZE];    if (entering) {
changelog view
@@ -1,3 +1,8 @@+cmark 0.5.2 (26 Mar 2016)++  * Add flag to allow building with an installed libcmark (Jens Petersen).+  * Updated to libcmark 0.25.2.+ cmark 0.5.1 (21 Jan 2016)    * Updated to libcmark 0.24.1.
cmark.cabal view
@@ -1,12 +1,12 @@ name:                cmark-version:             0.5.1+version:             0.5.2 synopsis:            Fast, accurate CommonMark (Markdown) parser and renderer description:   This package provides Haskell bindings for   <https://github.com/jgm/cmark libcmark>, the reference   parser for <http://commonmark.org CommonMark>, a fully   specified variant of Markdown. It includes sources for-  libcmark (0.24.1) and does not require prior installation of the+  libcmark (0.25.2) and does not require prior installation of the   C library.  homepage:            https://github.com/jgm/commonmark-hs@@ -16,7 +16,7 @@ maintainer:          jgm@berkeley.edu copyright:           (C) 2015 John MacFarlane category:            Text-tested-with:         GHC == 7.4.2, GHC == 7.6.3, GHC == 7.8.2+tested-with:         GHC == 7.4.2, GHC == 7.6.3, GHC == 7.8.2, GHC == 7.10.3 build-type:          Simple extra-source-files:  README.md                      changelog@@ -48,6 +48,10 @@   type:              git   location:          git://github.com/jgm/cmark-hs.git +flag pkgconfig+  default:     False+  description: Use system libcmark via pkgconfig+ library   exposed-modules:     CMark   build-depends:       base >=4.5 && < 4.9,@@ -57,10 +61,13 @@     build-depends:     ghc-prim >= 0.2   default-language:    Haskell2010   ghc-options:         -Wall -fno-warn-unused-do-bind-  cc-options:          -Wall -std=c99-  Include-dirs:        cbits-  Includes:            cmark.h-  c-sources:           cbits/houdini_html_u.c+  if flag(pkgconfig)+    pkgconfig-depends: libcmark+  else+    cc-options:        -Wall -std=c99+    Include-dirs:      cbits+    Includes:          cmark.h+    c-sources:         cbits/houdini_html_u.c                        cbits/references.c                        cbits/utf8.c                        cbits/inlines.c