cmark 0.5.1 → 0.5.2
raw patch · 17 files changed
+515/−287 lines, 17 files
Files
- README.md +2/−2
- cbits/blocks.c +406/−244
- cbits/buffer.c +1/−1
- cbits/cmark.h +3/−3
- cbits/cmark_version.h +2/−2
- cbits/commonmark.c +5/−5
- cbits/config.h +51/−0
- cbits/html.c +3/−3
- cbits/inlines.c +11/−11
- cbits/latex.c +2/−2
- cbits/man.c +1/−1
- cbits/node.c +2/−2
- cbits/parser.h +2/−0
- cbits/render.c +2/−2
- cbits/xml.c +3/−2
- changelog +5/−0
- cmark.cabal +14/−7
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