summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/blocks.c124
-rw-r--r--src/buffer.c25
-rw-r--r--src/buffer.h1
-rw-r--r--src/cmark.h1
-rw-r--r--src/html/html.c466
-rw-r--r--src/inlines.c272
-rw-r--r--src/references.c42
7 files changed, 568 insertions, 363 deletions
diff --git a/src/blocks.c b/src/blocks.c
index 7613c82..ccb84a7 100644
--- a/src/blocks.c
+++ b/src/blocks.c
@@ -20,14 +20,14 @@ static node_block* make_block(int tag, int start_line, int start_column)
node_block* e;
e = calloc(1, sizeof(*e));
- if(e != NULL) {
- e->tag = tag;
- e->open = true;
- e->start_line = start_line;
- e->start_column = start_column;
- e->end_line = start_line;
- strbuf_init(&e->string_content, 32);
- }
+ if(e != NULL) {
+ e->tag = tag;
+ e->open = true;
+ e->start_line = start_line;
+ e->start_column = start_column;
+ e->end_line = start_line;
+ strbuf_init(&e->string_content, 32);
+ }
return e;
}
@@ -252,12 +252,12 @@ static node_block* add_child(node_block* parent,
return child;
}
+
// Free a node_block list and any children.
void cmark_free_nodes(node_block *e)
{
node_block * next;
while (e != NULL) {
- next = e->next;
free_inlines(e->inline_content);
strbuf_free(&e->string_content);
if (e->tag == BLOCK_FENCED_CODE) {
@@ -265,31 +265,63 @@ void cmark_free_nodes(node_block *e)
} else if (e->tag == BLOCK_DOCUMENT) {
reference_map_free(e->as.document.refmap);
}
- cmark_free_nodes(e->children);
+ if (e->last_child) {
+ // Splice children into list
+ e->last_child->next = e->next;
+ e->next = e->children;
+ }
+ next = e->next;
free(e);
e = next;
}
}
+typedef struct BlockStack {
+ struct BlockStack *previous;
+ node_block *next_sibling;
+} block_stack;
+
// Walk through node_block and all children, recursively, parsing
// string content into inline content where appropriate.
void process_inlines(node_block* cur, reference_map *refmap)
{
- switch (cur->tag) {
- case BLOCK_PARAGRAPH:
- case BLOCK_ATX_HEADER:
- case BLOCK_SETEXT_HEADER:
- cur->inline_content = parse_inlines(&cur->string_content, refmap);
- break;
+ block_stack* stack = NULL;
+ block_stack* newstack = NULL;
+
+ while (cur != NULL) {
+ switch (cur->tag) {
+ case BLOCK_PARAGRAPH:
+ case BLOCK_ATX_HEADER:
+ case BLOCK_SETEXT_HEADER:
+ cur->inline_content = parse_inlines(&cur->string_content, refmap);
+ break;
- default:
- break;
- }
+ default:
+ break;
+ }
- node_block *child = cur->children;
- while (child != NULL) {
- process_inlines(child, refmap);
- child = child->next;
+ if (cur->children) {
+ newstack = (block_stack*)malloc(sizeof(block_stack));
+ if (newstack == NULL) return;
+ newstack->previous = stack;
+ stack = newstack;
+ stack->next_sibling = cur->next;
+ cur = cur->children;
+ } else {
+ cur = cur->next;
+ }
+
+ while (cur == NULL && stack != NULL) {
+ cur = stack->next_sibling;
+ newstack = stack->previous;
+ free(stack);
+ stack = newstack;
+ }
+ }
+ while (stack != NULL) {
+ newstack = stack->previous;
+ free(stack);
+ stack = newstack;
}
}
@@ -311,16 +343,16 @@ static int parse_list_marker(chunk *input, int pos, struct ListData ** dataptr)
return 0;
}
data = calloc(1, sizeof(*data));
- if(data == NULL) {
- return 0;
- } else {
- data->marker_offset = 0; // will be adjusted later
- data->list_type = bullet;
- data->bullet_char = c;
- data->start = 1;
- data->delimiter = period;
- data->tight = false;
- }
+ if(data == NULL) {
+ return 0;
+ } else {
+ data->marker_offset = 0; // will be adjusted later
+ data->list_type = bullet;
+ data->bullet_char = c;
+ data->start = 1;
+ data->delimiter = period;
+ data->tight = false;
+ }
} else if (isdigit(c)) {
int start = 0;
@@ -336,16 +368,16 @@ static int parse_list_marker(chunk *input, int pos, struct ListData ** dataptr)
return 0;
}
data = calloc(1, sizeof(*data));
- if(data == NULL) {
- return 0;
- } else {
- data->marker_offset = 0; // will be adjusted later
- data->list_type = ordered;
- data->bullet_char = 0;
- data->start = start;
- data->delimiter = (c == '.' ? period : parens);
- data->tight = false;
- }
+ if(data == NULL) {
+ return 0;
+ } else {
+ data->marker_offset = 0; // will be adjusted later
+ data->list_type = ordered;
+ data->bullet_char = 0;
+ data->start = start;
+ data->delimiter = (c == '.' ? period : parens);
+ data->tight = false;
+ }
} else {
return 0;
}
@@ -438,8 +470,8 @@ static void chop_trailing_hashtags(chunk *ch)
// Check for a be a space before the final #s:
if (n != orig_n && n >= 0 && peek_at(ch, n) == ' ') {
- ch->len = n;
- chunk_rtrim(ch);
+ ch->len = n;
+ chunk_rtrim(ch);
}
}
@@ -462,7 +494,7 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr)
// Add a newline to the end if not present:
if (line->ptr[line->size - 1] != '\n') {
- strbuf_putc(line, '\n');
+ strbuf_putc(line, '\n');
}
input.data = line->ptr;
input.len = line->size;
diff --git a/src/buffer.c b/src/buffer.c
index 1cdcae8..a5139fa 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -291,19 +291,11 @@ void strbuf_drop(strbuf *buf, int n)
}
}
-void strbuf_trim(strbuf *buf)
+void strbuf_rtrim(strbuf *buf)
{
- int i = 0;
-
if (!buf->size)
return;
- while (i < buf->size && isspace(buf->ptr[i]))
- i++;
-
- strbuf_drop(buf, i);
-
- /* rtrim */
while (buf->size > 0) {
if (!isspace(buf->ptr[buf->size - 1]))
break;
@@ -314,6 +306,21 @@ void strbuf_trim(strbuf *buf)
buf->ptr[buf->size] = '\0';
}
+void strbuf_trim(strbuf *buf)
+{
+ int i = 0;
+
+ if (!buf->size)
+ return;
+
+ while (i < buf->size && isspace(buf->ptr[i]))
+ i++;
+
+ strbuf_drop(buf, i);
+
+ strbuf_rtrim(buf);
+}
+
// Destructively modify string, collapsing consecutive
// space and newline characters into a single space.
void strbuf_normalize_whitespace(strbuf *s)
diff --git a/src/buffer.h b/src/buffer.h
index 1bc1eee..63d6202 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -107,6 +107,7 @@ int strbuf_strchr(const strbuf *buf, int c, int pos);
int strbuf_strrchr(const strbuf *buf, int c, int pos);
void strbuf_drop(strbuf *buf, int n);
void strbuf_truncate(strbuf *buf, int len);
+void strbuf_rtrim(strbuf *buf);
void strbuf_trim(strbuf *buf);
void strbuf_normalize_whitespace(strbuf *s);
void strbuf_unescape(strbuf *s);
diff --git a/src/cmark.h b/src/cmark.h
index e34df72..ff2f9a2 100644
--- a/src/cmark.h
+++ b/src/cmark.h
@@ -9,7 +9,6 @@
#define VERSION "0.1"
#define CODE_INDENT 4
-#define STACK_LIMIT 1000
struct node_inl {
enum {
diff --git a/src/html/html.c b/src/html/html.c
index fde1cb4..5f08506 100644
--- a/src/html/html.c
+++ b/src/html/html.c
@@ -8,6 +8,76 @@
#include "debug.h"
#include "html/houdini.h"
+typedef struct RenderStack {
+ struct RenderStack *previous;
+ char* literal;
+ union {
+ node_inl *inl;
+ node_block *block;
+ } next_sibling;
+ bool tight;
+ bool trim;
+} render_stack;
+
+static void free_render_stack(render_stack * rstack)
+{
+ render_stack * tempstack;
+ while (rstack) {
+ tempstack = rstack;
+ rstack = rstack->previous;
+ free(tempstack);
+ }
+}
+
+static render_stack* push_inline(render_stack* rstack,
+ node_inl* inl,
+ char* literal)
+{
+ render_stack* newstack;
+ newstack = (render_stack*)malloc(sizeof(render_stack));
+ if (newstack == NULL) {
+ return NULL;
+ }
+ newstack->previous = rstack;
+ newstack->next_sibling.inl = inl;
+ newstack->literal = literal;
+ newstack->tight = false;
+ newstack->trim = false;
+ return newstack;
+}
+
+static render_stack* push_block(render_stack* rstack,
+ node_block* block,
+ char* literal,
+ bool tight,
+ bool trim)
+{
+ render_stack* newstack;
+ newstack = (render_stack*)malloc(sizeof(render_stack));
+ if (newstack == NULL) {
+ return NULL;
+ }
+ newstack->previous = rstack;
+ newstack->next_sibling.block = block;
+ newstack->literal = literal;
+ newstack->tight = tight;
+ newstack->trim = trim;
+ return newstack;
+}
+
+static render_stack* pop_render_stack(render_stack* rstack)
+{
+ render_stack* top = rstack;
+
+ if (rstack == NULL) {
+ return NULL;
+ }
+ rstack = rstack->previous;
+ top->previous = NULL;
+ free_render_stack(top);
+ return rstack;
+}
+
// Functions to convert node_block and inline lists to HTML strings.
static void escape_html(strbuf *dest, const unsigned char *source, int length)
@@ -33,196 +103,276 @@ static inline void cr(strbuf *html)
}
// Convert an inline list to HTML. Returns 0 on success, and sets result.
-static void inlines_to_html(strbuf *html, node_inl* ils)
+static void inlines_to_plain_html(strbuf *html, node_inl* ils)
{
- strbuf scrap = GH_BUF_INIT;
+ node_inl* children;
+ bool visit_children;
+ render_stack* rstack = NULL;
while(ils != NULL) {
+ visit_children = false;
switch(ils->tag) {
- case INL_STRING:
- escape_html(html, ils->content.literal.data, ils->content.literal.len);
- break;
-
- case INL_LINEBREAK:
- strbuf_puts(html, "<br />\n");
- break;
-
- case INL_SOFTBREAK:
- strbuf_putc(html, '\n');
- break;
-
- case INL_CODE:
- strbuf_puts(html, "<code>");
- escape_html(html, ils->content.literal.data, ils->content.literal.len);
- strbuf_puts(html, "</code>");
- break;
-
- case INL_RAW_HTML:
- strbuf_put(html,
- ils->content.literal.data,
- ils->content.literal.len);
- break;
-
- case INL_LINK:
- strbuf_puts(html, "<a href=\"");
- if (ils->content.linkable.url)
- escape_href(html, ils->content.linkable.url, -1);
-
- if (ils->content.linkable.title) {
- strbuf_puts(html, "\" title=\"");
- escape_html(html, ils->content.linkable.title, -1);
- }
+ case INL_STRING:
+ case INL_CODE:
+ case INL_RAW_HTML:
+ escape_html(html, ils->content.literal.data, ils->content.literal.len);
+ break;
+
+ case INL_LINEBREAK:
+ case INL_SOFTBREAK:
+ strbuf_putc(html, '\n');
+ break;
+
+ case INL_LINK:
+ case INL_IMAGE:
+ children = ils->content.inlines;
+ visit_children = true;
+ rstack = push_inline(rstack, ils->next, "");
+ break;
+
+ case INL_STRONG:
+ case INL_EMPH:
+ children = ils->content.inlines;
+ visit_children = true;
+ rstack = push_inline(rstack, ils->next, "");
+ break;
+ }
+ if (visit_children) {
+ ils = children;
+ } else {
+ ils = ils->next;
+ }
+ while (ils == NULL && rstack != NULL) {
+ strbuf_puts(html, rstack->literal);
+ ils = rstack->next_sibling.inl;
+ rstack = pop_render_stack(rstack);
+ }
+ }
- strbuf_puts(html, "\">");
- inlines_to_html(html, ils->content.inlines);
- strbuf_puts(html, "</a>");
- break;
-
- case INL_IMAGE:
- strbuf_puts(html, "<img src=\"");
- if (ils->content.linkable.url)
- escape_href(html, ils->content.linkable.url, -1);
-
- inlines_to_html(&scrap, ils->content.inlines);
- strbuf_puts(html, "\" alt=\"");
- if (scrap.size)
- escape_html(html, scrap.ptr, scrap.size);
- strbuf_clear(&scrap);
-
- if (ils->content.linkable.title) {
- strbuf_puts(html, "\" title=\"");
- escape_html(html, ils->content.linkable.title, -1);
- }
+ free_render_stack(rstack);
+}
- strbuf_puts(html, "\"/>");
- break;
- case INL_STRONG:
- strbuf_puts(html, "<strong>");
- inlines_to_html(html, ils->content.inlines);
- strbuf_puts(html, "</strong>");
- break;
+// Convert an inline list to HTML. Returns 0 on success, and sets result.
+static void inlines_to_html(strbuf *html, node_inl* ils)
+{
+ node_inl* children;
+ render_stack* rstack = NULL;
- case INL_EMPH:
- strbuf_puts(html, "<em>");
- inlines_to_html(html, ils->content.inlines);
- strbuf_puts(html, "</em>");
- break;
+ while(ils != NULL) {
+ children = NULL;
+ switch(ils->tag) {
+ case INL_STRING:
+ escape_html(html, ils->content.literal.data, ils->content.literal.len);
+ break;
+
+ case INL_LINEBREAK:
+ strbuf_puts(html, "<br />\n");
+ break;
+
+ case INL_SOFTBREAK:
+ strbuf_putc(html, '\n');
+ break;
+
+ case INL_CODE:
+ strbuf_puts(html, "<code>");
+ escape_html(html, ils->content.literal.data, ils->content.literal.len);
+ strbuf_puts(html, "</code>");
+ break;
+
+ case INL_RAW_HTML:
+ strbuf_put(html,
+ ils->content.literal.data,
+ ils->content.literal.len);
+ break;
+
+ case INL_LINK:
+ strbuf_puts(html, "<a href=\"");
+ if (ils->content.linkable.url)
+ escape_href(html, ils->content.linkable.url, -1);
+
+ if (ils->content.linkable.title) {
+ strbuf_puts(html, "\" title=\"");
+ escape_html(html, ils->content.linkable.title, -1);
+ }
+
+ strbuf_puts(html, "\">");
+ children = ils->content.inlines;
+ rstack = push_inline(rstack, ils->next, "</a>");
+ break;
+
+ case INL_IMAGE:
+ strbuf_puts(html, "<img src=\"");
+ if (ils->content.linkable.url)
+ escape_href(html, ils->content.linkable.url, -1);
+
+ strbuf_puts(html, "\" alt=\"");
+ inlines_to_plain_html(html, ils->content.inlines);
+
+ if (ils->content.linkable.title) {
+ strbuf_puts(html, "\" title=\"");
+ escape_html(html, ils->content.linkable.title, -1);
+ }
+
+ strbuf_puts(html, "\"/>");
+ break;
+
+ case INL_STRONG:
+ strbuf_puts(html, "<strong>");
+ children = ils->content.inlines;
+ rstack = push_inline(rstack, ils->next, "</strong>");
+ break;
+
+ case INL_EMPH:
+ strbuf_puts(html, "<em>");
+ children = ils->content.inlines;
+ rstack = push_inline(rstack, ils->next, "</em>");
+ break;
+ }
+ if (children) {
+ ils = children;
+ } else {
+ ils = ils->next;
+ }
+ while (ils == NULL && rstack != NULL) {
+ strbuf_puts(html, rstack->literal);
+ ils = rstack->next_sibling.inl;
+ rstack = pop_render_stack(rstack);
}
- ils = ils->next;
}
- strbuf_free(&scrap);
+ free_render_stack(rstack);
}
// Convert a node_block list to HTML. Returns 0 on success, and sets result.
-static void blocks_to_html(strbuf *html, node_block *b, bool tight)
+static void blocks_to_html(strbuf *html, node_block *b)
{
struct ListData *data;
+ render_stack* rstack = NULL;
+ bool visit_children = false;
+ bool tight = false;
while(b != NULL) {
+ visit_children = false;
switch(b->tag) {
- case BLOCK_DOCUMENT:
- blocks_to_html(html, b->children, false);
- break;
-
- case BLOCK_PARAGRAPH:
- if (tight) {
- inlines_to_html(html, b->inline_content);
- } else {
- cr(html);
- strbuf_puts(html, "<p>");
- inlines_to_html(html, b->inline_content);
- strbuf_puts(html, "</p>\n");
- }
- break;
-
- case BLOCK_BQUOTE:
- cr(html);
- strbuf_puts(html, "<blockquote>\n");
- blocks_to_html(html, b->children, false);
- strbuf_puts(html, "</blockquote>\n");
- break;
-
- case BLOCK_LIST_ITEM:
- cr(html);
- strbuf_puts(html, "<li>");
- blocks_to_html(html, b->children, tight);
- strbuf_trim(html); /* TODO: rtrim */
- strbuf_puts(html, "</li>\n");
- break;
-
- case BLOCK_LIST:
- // make sure a list starts at the beginning of the line:
- cr(html);
- data = &(b->as.list);
-
- if (data->start > 1) {
- strbuf_printf(html, "<%s start=\"%d\">\n",
- data->list_type == bullet ? "ul" : "ol",
- data->start);
- } else {
- strbuf_puts(html, data->list_type == bullet ? "<ul>\n" : "<ol>\n");
- }
+ case BLOCK_DOCUMENT:
+ rstack = push_block(rstack, b->next, "", false, false);
+ visit_children = true;
+ break;
- blocks_to_html(html, b->children, data->tight);
- strbuf_puts(html, data->list_type == bullet ? "</ul>" : "</ol>");
- strbuf_putc(html, '\n');
- break;
-
- case BLOCK_ATX_HEADER:
- case BLOCK_SETEXT_HEADER:
- cr(html);
- strbuf_printf(html, "<h%d>", b->as.header.level);
+ case BLOCK_PARAGRAPH:
+ if (tight) {
inlines_to_html(html, b->inline_content);
- strbuf_printf(html, "</h%d>\n", b->as.header.level);
- break;
-
- case BLOCK_INDENTED_CODE:
- case BLOCK_FENCED_CODE:
+ } else {
cr(html);
-
- strbuf_puts(html, "<pre><code");
-
- if (b->tag == BLOCK_FENCED_CODE) {
- strbuf *info = &b->as.code.info;
-
- if (strbuf_len(info) > 0) {
- int first_tag = strbuf_strchr(info, ' ', 0);
- if (first_tag < 0)
- first_tag = strbuf_len(info);
-
- strbuf_puts(html, " class=\"language-");
- escape_html(html, info->ptr, first_tag);
- strbuf_putc(html, '"');
- }
+ strbuf_puts(html, "<p>");
+ inlines_to_html(html, b->inline_content);
+ strbuf_puts(html, "</p>\n");
+ }
+ break;
+
+ case BLOCK_BQUOTE:
+ cr(html);
+ strbuf_puts(html, "<blockquote>\n");
+ rstack = push_block(rstack, b->next, "</blockquote>\n", tight, false);
+ tight = false;
+ visit_children = true;
+ break;
+
+ case BLOCK_LIST_ITEM:
+ cr(html);
+ strbuf_puts(html, "<li>");
+ rstack = push_block(rstack, b->next, "</li>\n", tight, true);
+ visit_children = true;
+ break;
+
+ case BLOCK_LIST:
+ // make sure a list starts at the beginning of the line:
+ cr(html);
+ data = &(b->as.list);
+
+ if (data->start > 1) {
+ strbuf_printf(html, "<%s start=\"%d\">\n",
+ data->list_type == bullet ? "ul" : "ol",
+ data->start);
+ } else {
+ strbuf_puts(html, data->list_type == bullet ? "<ul>\n" : "<ol>\n");
+ }
+
+ rstack = push_block(rstack, b->next,
+ data->list_type == bullet ?
+ "\n</ul>\n" : "\n</ol>\n", tight, false);
+ tight = data->tight;
+ visit_children = true;
+ break;
+
+ case BLOCK_ATX_HEADER:
+ case BLOCK_SETEXT_HEADER:
+ cr(html);
+ strbuf_printf(html, "<h%d>", b->as.header.level);
+ inlines_to_html(html, b->inline_content);
+ strbuf_printf(html, "</h%d>\n", b->as.header.level);
+ break;
+
+ case BLOCK_INDENTED_CODE:
+ case BLOCK_FENCED_CODE:
+ cr(html);
+
+ strbuf_puts(html, "<pre><code");
+
+ if (b->tag == BLOCK_FENCED_CODE) {
+ strbuf *info = &b->as.code.info;
+
+ if (strbuf_len(info) > 0) {
+ int first_tag = strbuf_strchr(info, ' ', 0);
+ if (first_tag < 0)
+ first_tag = strbuf_len(info);
+
+ strbuf_puts(html, " class=\"language-");
+ escape_html(html, info->ptr, first_tag);
+ strbuf_putc(html, '"');
}
+ }
- strbuf_putc(html, '>');
- escape_html(html, b->string_content.ptr, b->string_content.size);
- strbuf_puts(html, "</code></pre>\n");
- break;
+ strbuf_putc(html, '>');
+ escape_html(html, b->string_content.ptr, b->string_content.size);
+ strbuf_puts(html, "</code></pre>\n");
+ break;
- case BLOCK_HTML:
- strbuf_put(html, b->string_content.ptr, b->string_content.size);
- break;
+ case BLOCK_HTML:
+ strbuf_put(html, b->string_content.ptr, b->string_content.size);
+ break;
- case BLOCK_HRULE:
- strbuf_puts(html, "<hr />\n");
- break;
+ case BLOCK_HRULE:
+ strbuf_puts(html, "<hr />\n");
+ break;
- case BLOCK_REFERENCE_DEF:
- break;
+ case BLOCK_REFERENCE_DEF:
+ break;
- default:
- assert(false);
+ default:
+ assert(false);
+ }
+ if (visit_children) {
+ b = b->children;
+ } else {
+ b = b->next;
+ }
+ while (b == NULL && rstack != NULL) {
+ strbuf_puts(html, rstack->literal);
+ if (rstack->trim) {
+ strbuf_rtrim(html);
+ }
+ tight = rstack->tight;
+ b = rstack->next_sibling.block;
+ rstack = pop_render_stack(rstack);
}
-
- b = b->next;
}
+
+ free_render_stack(rstack);
}
void cmark_render_html(strbuf *html, node_block *root)
{
- blocks_to_html(html, root, false);
+ blocks_to_html(html, root);
}
diff --git a/src/inlines.c b/src/inlines.c
index 9216979..810230c 100644
--- a/src/inlines.c
+++ b/src/inlines.c
@@ -9,6 +9,7 @@
#include "utf8.h"
#include "scanners.h"
#include "inlines.h"
+#include "debug.h"
typedef struct InlineStack {
struct InlineStack *previous;
@@ -41,9 +42,9 @@ static unsigned char *bufdup(const unsigned char *buf)
if (buf) {
int len = strlen((char *)buf);
new = calloc(len + 1, sizeof(*new));
- if(new != NULL) {
- memcpy(new, buf, len + 1);
- }
+ if(new != NULL) {
+ memcpy(new, buf, len + 1);
+ }
}
return new;
@@ -52,13 +53,13 @@ static unsigned char *bufdup(const unsigned char *buf)
static inline node_inl *make_link_(node_inl *label, unsigned char *url, unsigned char *title)
{
node_inl* e = calloc(1, sizeof(*e));
- if(e != NULL) {
- e->tag = INL_LINK;
- e->content.linkable.label = label;
- e->content.linkable.url = url;
- e->content.linkable.title = title;
- e->next = NULL;
- }
+ if(e != NULL) {
+ e->tag = INL_LINK;
+ e->content.linkable.label = label;
+ e->content.linkable.url = url;
+ e->content.linkable.title = title;
+ e->next = NULL;
+ }
return e;
}
@@ -81,11 +82,11 @@ inline static node_inl* make_link(node_inl* label, chunk url, chunk title)
inline static node_inl* make_inlines(int t, node_inl* contents)
{
node_inl * e = calloc(1, sizeof(*e));
- if(e != NULL) {
- e->tag = t;
- e->content.inlines = contents;
- e->next = NULL;
- }
+ if(e != NULL) {
+ e->tag = t;
+ e->content.inlines = contents;
+ e->next = NULL;
+ }
return e;
}
@@ -93,11 +94,11 @@ inline static node_inl* make_inlines(int t, node_inl* contents)
inline static node_inl* make_literal(int t, chunk s)
{
node_inl * e = calloc(1, sizeof(*e));
- if(e != NULL) {
- e->tag = t;
- e->content.literal = s;
- e->next = NULL;
- }
+ if(e != NULL) {
+ e->tag = t;
+ e->content.literal = s;
+ e->next = NULL;
+ }
return e;
}
@@ -105,10 +106,10 @@ inline static node_inl* make_literal(int t, chunk s)
inline static node_inl* make_simple(int t)
{
node_inl* e = calloc(1, sizeof(*e));
- if(e != NULL) {
- e->tag = t;
- e->next = NULL;
- }
+ if(e != NULL) {
+ e->tag = t;
+ e->next = NULL;
+ }
return e;
}
@@ -121,10 +122,28 @@ inline static node_inl* make_simple(int t)
#define make_emph(contents) make_inlines(INL_EMPH, contents)
#define make_strong(contents) make_inlines(INL_STRONG, contents)
-// Free an inline list.
+// Utility function used by free_inlines
+void splice_into_list(node_inl* e, node_inl* children) {
+ node_inl * tmp;
+ if (children) {
+ tmp = children;
+ // Find last child
+ while (tmp->next) {
+ tmp = tmp->next;
+ }
+ // Splice children into list
+ tmp->next = e->next;
+ e->next = children;
+ }
+ return ;
+}
+
+// Free an inline list. Avoid recursion to prevent stack overflows
+// on deeply nested structures.
extern void free_inlines(node_inl* e)
{
node_inl * next;
+
while (e != NULL) {
switch (e->tag){
case INL_STRING:
@@ -139,13 +158,14 @@ extern void free_inlines(node_inl* e)
case INL_IMAGE:
free(e->content.linkable.url);
free(e->content.linkable.title);
- free_inlines(e->content.linkable.label);
+ splice_into_list(e, e->content.linkable.label);
break;
case INL_EMPH:
case INL_STRONG:
- free_inlines(e->content.inlines);
+ splice_into_list(e, e->content.inlines);
break;
default:
+ log_warn("Unknown inline tag %d", e->tag);
break;
}
next = e->next;
@@ -297,8 +317,8 @@ static int scan_delims(subject* subj, unsigned char c, bool * can_open, bool * c
advance(subj);
}
char_after = peek_char(subj);
- *can_open = numdelims > 0 && numdelims <= 3 && !isspace(char_after);
- *can_close = numdelims > 0 && numdelims <= 3 && !isspace(char_before);
+ *can_open = numdelims > 0 && !isspace(char_after);
+ *can_close = numdelims > 0 && !isspace(char_before);
if (c == '_') {
*can_open = *can_open && !isalnum(char_before);
*can_close = *can_close && !isalnum(char_after);
@@ -308,13 +328,13 @@ static int scan_delims(subject* subj, unsigned char c, bool * can_open, bool * c
static void free_openers(subject* subj, inline_stack* istack)
{
- inline_stack * tempstack;
- while (subj->emphasis_openers != istack) {
- tempstack = subj->emphasis_openers;
- subj->emphasis_openers = subj->emphasis_openers->previous;
- subj->emphasis_nestlevel--;
- free(tempstack);
- }
+ inline_stack * tempstack;
+ while (subj->emphasis_openers != istack) {
+ tempstack = subj->emphasis_openers;
+ subj->emphasis_openers = subj->emphasis_openers->previous;
+ subj->emphasis_nestlevel--;
+ free(tempstack);
+ }
}
// Parse strong/emph or a fallback.
@@ -324,6 +344,7 @@ static node_inl* handle_strong_emph(subject* subj, unsigned char c, node_inl **l
bool can_open, can_close;
int numdelims;
int useDelims;
+ int openerDelims;
inline_stack * istack;
node_inl * inl;
node_inl * emph;
@@ -332,81 +353,84 @@ static node_inl* handle_strong_emph(subject* subj, unsigned char c, node_inl **l
numdelims = scan_delims(subj, c, &can_open, &can_close);
if (can_close)
- {
- // walk the stack and find a matching opener, if there is one
- istack = subj->emphasis_openers;
- while (true)
{
- if (istack == NULL)
- goto cannotClose;
-
- if (istack->delim_char == c)
- break;
+ // walk the stack and find a matching opener, if there is one
+ istack = subj->emphasis_openers;
+ while (true)
+ {
+ if (istack == NULL)
+ goto cannotClose;
+
+ if (istack->delim_char == c)
+ break;
+
+ istack = istack->previous;
+ }
+
+ // calculate the actual number of delimeters used from this closer
+ openerDelims = istack->delim_count;
+ if (numdelims < 3 || openerDelims < 3) {
+ useDelims = numdelims <= openerDelims ? numdelims : openerDelims;
+ } else { // (numdelims >= 3 && openerDelims >= 3)
+ useDelims = numdelims % 2 == 0 ? 2 : 1;
+ }
- istack = istack->previous;
+ if (istack->delim_count == useDelims)
+ {
+ // the opener is completely used up - remove the stack entry and reuse the inline element
+ inl = istack->first_inline;
+ inl->tag = useDelims == 1 ? INL_EMPH : INL_STRONG;
+ chunk_free(&inl->content.literal);
+ inl->content.inlines = inl->next;
+ inl->next = NULL;
+
+ // remove this opener and all later ones from stack:
+ free_openers(subj, istack->previous);
+ *last = inl;
+ }
+ else
+ {
+ // the opener will only partially be used - stack entry remains (truncated) and a new inline is added.
+ inl = istack->first_inline;
+ istack->delim_count -= useDelims;
+ inl->content.literal.len = istack->delim_count;
+
+ emph = useDelims == 1 ? make_emph(inl->next) : make_strong(inl->next);
+ inl->next = emph;
+
+ // remove all later openers from stack:
+ free_openers(subj, istack);
+
+ *last = emph;
+ }
+
+ // if the closer was not fully used, move back a char or two and try again.
+ if (useDelims < numdelims)
+ {
+ subj->pos = subj->pos - numdelims + useDelims;
+ return NULL;
+ }
+
+ return NULL; // make_str(chunk_literal(""));
}
- // calculate the actual number of delimeters used from this closer
- useDelims = istack->delim_count;
- if (useDelims == 3) useDelims = numdelims == 3 ? 1 : numdelims;
- else if (useDelims > numdelims) useDelims = 1;
-
- if (istack->delim_count == useDelims)
- {
- // the opener is completely used up - remove the stack entry and reuse the inline element
- inl = istack->first_inline;
- inl->tag = useDelims == 1 ? INL_EMPH : INL_STRONG;
- chunk_free(&inl->content.literal);
- inl->content.inlines = inl->next;
- inl->next = NULL;
-
- // remove this opener and all later ones from stack:
- free_openers(subj, istack->previous);
- *last = inl;
- }
- else
- {
- // the opener will only partially be used - stack entry remains (truncated) and a new inline is added.
- inl = istack->first_inline;
- istack->delim_count -= useDelims;
- inl->content.literal.len = istack->delim_count;
-
- emph = useDelims == 1 ? make_emph(inl->next) : make_strong(inl->next);
- inl->next = emph;
-
- // remove all later openers from stack:
- free_openers(subj, istack);
-
- *last = emph;
- }
+ cannotClose:
+ inl_text = make_str(chunk_dup(&subj->input, subj->pos - numdelims, numdelims));
- // if the closer was not fully used, move back a char or two and try again.
- if (useDelims < numdelims)
+ if (can_open)
{
- subj->pos = subj->pos - numdelims + useDelims;
- return handle_strong_emph(subj, c, last);
+ istack = (inline_stack*)malloc(sizeof(inline_stack));
+ if (istack == NULL) {
+ return NULL;
+ }
+ istack->delim_count = numdelims;
+ istack->delim_char = c;
+ istack->first_inline = inl_text;
+ istack->previous = subj->emphasis_openers;
+ subj->emphasis_openers = istack;
+ subj->emphasis_nestlevel++;
}
- return NULL; // make_str(chunk_literal(""));
- }
-
-cannotClose:
- inl_text = make_str(chunk_dup(&subj->input, subj->pos - numdelims, numdelims));
-
- if (can_open && subj->emphasis_nestlevel < STACK_LIMIT)
- {
- istack = (inline_stack*)malloc(sizeof(inline_stack));
- if (istack == NULL) {
- return NULL;
- }
- istack->delim_count = numdelims;
- istack->delim_char = c;
- istack->first_inline = inl_text;
- istack->previous = subj->emphasis_openers;
- subj->emphasis_openers = istack;
- subj->emphasis_nestlevel++;
- }
-
return inl_text;
}
@@ -438,7 +462,7 @@ static node_inl* handle_entity(subject* subj)
len = houdini_unescape_ent(&ent,
subj->input.data + subj->pos,
subj->input.len - subj->pos
- );
+ );
if (len == 0)
return make_str(chunk_literal("&"));
@@ -538,9 +562,9 @@ static node_inl* handle_pointy_brace(subject* subj)
subj->pos += matchlen;
return make_autolink(
- make_str_with_entities(&contents),
- contents, 0
- );
+ make_str_with_entities(&contents),
+ contents, 0
+ );
}
// next try to match an email autolink
@@ -550,9 +574,9 @@ static node_inl* handle_pointy_brace(subject* subj)
subj->pos += matchlen;
return make_autolink(
- make_str_with_entities(&contents),
- contents, 1
- );
+ make_str_with_entities(&contents),
+ contents, 1
+ );
}
// finally, try to match an html tag
@@ -594,8 +618,7 @@ static int link_label(subject* subj, chunk *raw_label)
advance(subj); // advance past [
unsigned char c;
- while ((c = peek_char(subj)) &&
- (c != ']' || (nestlevel > 0 && nestlevel < STACK_LIMIT))) {
+ while ((c = peek_char(subj)) && (c != ']' || nestlevel > 0)) {
switch (c) {
case '`':
tmp = handle_backticks(subj);
@@ -646,11 +669,12 @@ static node_inl* handle_left_bracket(subject* subj)
int n;
int sps;
int found_label;
- int endlabel, starturl, endurl, starttitle, endtitle, endall;
+ int endlabel, startpos, starturl, endurl, starttitle, endtitle, endall;
chunk rawlabel;
chunk url, title;
+ startpos = subj->pos;
found_label = link_label(subj, &rawlabel);
endlabel = subj->pos;
@@ -679,13 +703,7 @@ static node_inl* handle_left_bracket(subject* subj)
return make_link(lab, url, title);
} else {
- // if we get here, we matched a label but didn't get further:
- subj->pos = endlabel;
- lab = parse_chunk_inlines(&rawlabel, subj->refmap);
- result = append_inlines(make_str(chunk_literal("[")),
- append_inlines(lab,
- make_str(chunk_literal("]"))));
- return result;
+ goto noMatch;
}
} else {
chunk rawlabel_tmp;
@@ -710,16 +728,14 @@ static node_inl* handle_left_bracket(subject* subj)
lab = parse_chunk_inlines(&rawlabel, NULL);
result = make_ref_link(lab, ref);
} else {
- subj->pos = endlabel;
- lab = parse_chunk_inlines(&rawlabel, subj->refmap);
- result = append_inlines(make_str(chunk_literal("[")),
- append_inlines(lab, make_str(chunk_literal("]"))));
+ goto noMatch;
}
return result;
}
}
+noMatch:
// If we fall through to here, it means we didn't match a link:
- advance(subj); // advance past [
+ subj->pos = startpos + 1; // advance past [
return make_str(chunk_literal("["));
}
@@ -755,9 +771,9 @@ extern node_inl* parse_inlines_while(subject* subj, int (*f)(subject*))
node_inl** last = &result;
node_inl* first = NULL;
while ((*f)(subj) && parse_inline(subj, last)) {
- if (!first) {
- first = *last;
- }
+ if (!first) {
+ first = *last;
+ }
}
inline_stack* istack = subj->emphasis_openers;
diff --git a/src/references.c b/src/references.c
index 04b9025..5ba4b24 100644
--- a/src/references.c
+++ b/src/references.c
@@ -16,12 +16,12 @@ refhash(const unsigned char *link_ref)
static void reference_free(reference *ref)
{
- if(ref != NULL) {
- free(ref->label);
- free(ref->url);
- free(ref->title);
- free(ref);
- }
+ if(ref != NULL) {
+ free(ref->label);
+ free(ref->url);
+ free(ref->title);
+ free(ref);
+ }
}
// normalize reference: collapse internal whitespace to single space,
@@ -33,8 +33,8 @@ static unsigned char *normalize_reference(chunk *ref)
strbuf normalized = GH_BUF_INIT;
unsigned char *result;
- if(ref == NULL)
- return NULL;
+ if(ref == NULL)
+ return NULL;
if (ref->len == 0)
return NULL;
@@ -50,7 +50,7 @@ static unsigned char *normalize_reference(chunk *ref)
free(result);
return NULL;
}
-
+
return result;
}
@@ -81,15 +81,15 @@ extern void reference_create(reference_map *map, chunk *label, chunk *url, chunk
return;
ref = calloc(1, sizeof(*ref));
- if(ref != NULL) {
- ref->label = reflabel;
- ref->hash = refhash(ref->label);
- ref->url = clean_url(url);
- ref->title = clean_title(title);
- ref->next = NULL;
-
- add_reference(map, ref);
- }
+ if(ref != NULL) {
+ ref->label = reflabel;
+ ref->hash = refhash(ref->label);
+ ref->url = clean_url(url);
+ ref->title = clean_title(title);
+ ref->next = NULL;
+
+ add_reference(map, ref);
+ }
}
// Returns reference if refmap contains a reference with matching
@@ -125,8 +125,8 @@ void reference_map_free(reference_map *map)
{
unsigned int i;
- if(map == NULL)
- return;
+ if(map == NULL)
+ return;
for (i = 0; i < REFMAP_SIZE; ++i) {
reference *ref = map->table[i];
@@ -144,5 +144,5 @@ void reference_map_free(reference_map *map)
reference_map *reference_map_new(void)
{
- return calloc(1, sizeof(reference_map));
+ return calloc(1, sizeof(reference_map));
}