cmark

My personal build of CMark ✏️

Commit
f3f50b29d615d2678d8047dc277b108cc5143167
Parent
3ef0718f9f4c9dea5014a8a0e9a67e2366b9374f
Author
Nick Wellnhofer <wellnhofer@aevum.de>
Date

Rearrange struct cmark_node

Introduce multi-purpose data/len members in struct cmark_node. This is mainly used to store literal text for inlines, code and HTML blocks.

Move the content strbuf for blocks from cmark_node to cmark_parser. When finalizing nodes that allow inlines (paragraphs and headings), detach the strbuf and store the block content in the node's data/len members. Free the block content after processing inlines.

Reduces size of struct cmark_node by 8 bytes.

Diffstat

13 files changed, 111 insertions, 174 deletions

Status File Name N° Changes Insertions Deletions
Modified src/blocks.c 53 30 23
Modified src/chunk.h 65 5 60
Modified src/commonmark.c 2 1 1
Modified src/html.c 15 7 8
Modified src/inlines.c 62 31 31
Modified src/iterator.c 12 6 6
Modified src/latex.c 6 3 3
Modified src/main.c 2 1 1
Modified src/node.c 42 17 25
Modified src/node.h 16 5 11
Modified src/parser.h 1 1 0
Modified src/render.c 2 1 1
Modified src/xml.c 7 3 4
diff --git a/src/blocks.c b/src/blocks.c
@@ -72,7 +72,7 @@ static cmark_node *make_block(cmark_mem *mem, cmark_node_type tag,
   cmark_node *e;
 
   e = (cmark_node *)mem->calloc(1, sizeof(*e));
-  cmark_strbuf_init(mem, &e->content, 32);
+  e->mem = mem;
   e->type = (uint16_t)tag;
   e->flags = CMARK_NODE__OPEN;
   e->start_line = start_line;
@@ -96,6 +96,7 @@ cmark_parser *cmark_parser_new_with_mem(int options, cmark_mem *mem) {
 
   cmark_strbuf_init(mem, &parser->curline, 256);
   cmark_strbuf_init(mem, &parser->linebuf, 0);
+  cmark_strbuf_init(mem, &parser->content, 0);
 
   parser->refmap = cmark_reference_map_new(mem);
   parser->root = document;
@@ -171,19 +172,18 @@ static CMARK_INLINE bool contains_inlines(cmark_node_type block_type) {
           block_type == CMARK_NODE_HEADING);
 }
 
-static void add_line(cmark_node *node, cmark_chunk *ch, cmark_parser *parser) {
+static void add_line(cmark_chunk *ch, cmark_parser *parser) {
   int chars_to_tab;
   int i;
-  assert(node->flags & CMARK_NODE__OPEN);
   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->content, ' ');
+      cmark_strbuf_putc(&parser->content, ' ');
     }
   }
-  cmark_strbuf_put(&node->content, ch->data + parser->offset,
+  cmark_strbuf_put(&parser->content, ch->data + parser->offset,
                    ch->len - parser->offset);
 }
 
@@ -230,12 +230,10 @@ static bool S_ends_with_blank_line(cmark_node *node) {
 }
 
 // returns true if content remains after link defs are resolved.
-static bool resolve_reference_link_definitions(
-		cmark_parser *parser,
-                cmark_node *b) {
+static bool resolve_reference_link_definitions(cmark_parser *parser) {
   bufsize_t pos;
-  cmark_strbuf *node_content = &b->content;
-  cmark_chunk chunk = {node_content->ptr, node_content->size, 0};
+  cmark_strbuf *node_content = &parser->content;
+  cmark_chunk chunk = {node_content->ptr, node_content->size};
   while (chunk.len && chunk.data[0] == '[' &&
          (pos = cmark_parse_reference_inline(parser->mem, &chunk,
 					     parser->refmap))) {
@@ -244,7 +242,7 @@ static bool resolve_reference_link_definitions(
     chunk.len -= pos;
   }
   cmark_strbuf_drop(node_content, (node_content->size - chunk.len));
-  return !is_blank(&b->content, 0);
+  return !is_blank(node_content, 0);
 }
 
 static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
@@ -277,15 +275,18 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
     b->end_column = parser->last_line_length;
   }
 
-  cmark_strbuf *node_content = &b->content;
+  cmark_strbuf *node_content = &parser->content;
 
   switch (S_type(b)) {
   case CMARK_NODE_PARAGRAPH:
   {
-    has_content = resolve_reference_link_definitions(parser, b);
+    has_content = resolve_reference_link_definitions(parser);
     if (!has_content) {
       // remove blank node (former reference def)
       cmark_node_free(b);
+    } else {
+      b->len = node_content->size;
+      b->data = cmark_strbuf_detach(node_content);
     }
     break;
   }
@@ -318,12 +319,14 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
         pos += 1;
       cmark_strbuf_drop(node_content, pos);
     }
-    b->as.code.literal = cmark_strbuf_detach(node_content);
+    b->len = node_content->size;
+    b->data = cmark_strbuf_detach(node_content);
     break;
 
+  case CMARK_NODE_HEADING:
   case CMARK_NODE_HTML_BLOCK:
-    b->as.literal.len = node_content->size;
-    b->as.literal.data = cmark_strbuf_detach(node_content);
+    b->len = node_content->size;
+    b->data = cmark_strbuf_detach(node_content);
     break;
 
   case CMARK_NODE_LIST:      // determine tight/loose status
@@ -401,6 +404,9 @@ static void process_inlines(cmark_mem *mem, cmark_node *root,
     if (ev_type == CMARK_EVENT_ENTER) {
       if (contains_inlines(S_type(cur))) {
         cmark_parse_inlines(mem, cur, refmap, options);
+        mem->free(cur->data);
+        cur->data = NULL;
+        cur->len = 0;
       }
     }
   }
@@ -513,6 +519,8 @@ static cmark_node *finalize_document(cmark_parser *parser) {
   finalize(parser, parser->root);
   process_inlines(parser->mem, parser->root, parser->refmap, parser->options);
 
+  cmark_strbuf_free(&parser->content);
+
   return parser->root;
 }
 
@@ -996,7 +1004,7 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
                (lev =
                     scan_setext_heading_line(input, parser->first_nonspace))) {
       // finalize paragraph, resolving reference links
-      has_content = resolve_reference_link_definitions(parser, *container);
+      has_content = resolve_reference_link_definitions(parser);
 
       if (has_content) {
 
@@ -1136,7 +1144,7 @@ static void add_text_to_container(cmark_parser *parser, cmark_node *container,
   if (parser->current != last_matched_container &&
       container == last_matched_container && !parser->blank &&
       S_type(parser->current) == CMARK_NODE_PARAGRAPH) {
-    add_line(parser->current, input, parser);
+    add_line(input, parser);
   } else { // not a lazy continuation
     // Finalize any blocks that were not matched and set cur to container:
     while (parser->current != last_matched_container) {
@@ -1145,9 +1153,9 @@ static void add_text_to_container(cmark_parser *parser, cmark_node *container,
     }
 
     if (S_type(container) == CMARK_NODE_CODE_BLOCK) {
-      add_line(container, input, parser);
+      add_line(input, parser);
     } else if (S_type(container) == CMARK_NODE_HTML_BLOCK) {
-      add_line(container, input, parser);
+      add_line(input, parser);
 
       int matches_end_condition;
       switch (container->as.html_block_type) {
@@ -1194,14 +1202,14 @@ static void add_text_to_container(cmark_parser *parser, cmark_node *container,
       }
       S_advance_offset(parser, input, parser->first_nonspace - parser->offset,
                        false);
-      add_line(container, input, parser);
+      add_line(input, parser);
     } else {
       // create paragraph container for line
       container = add_child(parser, container, CMARK_NODE_PARAGRAPH,
                             parser->first_nonspace + 1);
       S_advance_offset(parser, input, parser->first_nonspace - parser->offset,
                        false);
-      add_line(container, input, parser);
+      add_line(input, parser);
     }
 
     parser->current = container;
@@ -1238,7 +1246,6 @@ static void S_process_line(cmark_parser *parser, const unsigned char *buffer,
 
   input.data = parser->curline.ptr;
   input.len = parser->curline.size;
-  input.alloc = 0;
 
   parser->line_number++;
 
diff --git a/src/chunk.h b/src/chunk.h
@@ -9,26 +9,19 @@
 #include "cmark_ctype.h"
 
 #define CMARK_CHUNK_EMPTY                                                      \
-  { NULL, 0, 0 }
+  { NULL, 0 }
 
 typedef struct {
-  unsigned char *data;
+  const unsigned char *data;
   bufsize_t len;
-  bufsize_t alloc; // also implies a NULL-terminated string
 } cmark_chunk;
 
-static CMARK_INLINE void cmark_chunk_free(cmark_mem *mem, cmark_chunk *c) {
-  if (c->alloc)
-    mem->free(c->data);
-
+static CMARK_INLINE void cmark_chunk_free(cmark_chunk *c) {
   c->data = NULL;
-  c->alloc = 0;
   c->len = 0;
 }
 
 static CMARK_INLINE void cmark_chunk_ltrim(cmark_chunk *c) {
-  assert(!c->alloc);
-
   while (c->len && cmark_isspace(c->data[0])) {
     c->data++;
     c->len--;
@@ -36,8 +29,6 @@ static CMARK_INLINE void cmark_chunk_ltrim(cmark_chunk *c) {
 }
 
 static CMARK_INLINE void cmark_chunk_rtrim(cmark_chunk *c) {
-  assert(!c->alloc);
-
   while (c->len > 0) {
     if (!cmark_isspace(c->data[c->len - 1]))
       break;
@@ -58,61 +49,15 @@ static CMARK_INLINE bufsize_t cmark_chunk_strchr(cmark_chunk *ch, int c,
   return p ? (bufsize_t)(p - ch->data) : ch->len;
 }
 
-static CMARK_INLINE const char *cmark_chunk_to_cstr(cmark_mem *mem,
-                                                    cmark_chunk *c) {
-  unsigned char *str;
-
-  if (c->alloc) {
-    return (char *)c->data;
-  }
-  str = (unsigned char *)mem->calloc(c->len + 1, 1);
-  if (c->len > 0) {
-    memcpy(str, c->data, c->len);
-  }
-  str[c->len] = 0;
-  c->data = str;
-  c->alloc = 1;
-
-  return (char *)str;
-}
-
-static CMARK_INLINE void cmark_chunk_set_cstr(cmark_mem *mem, cmark_chunk *c,
-                                              const char *str) {
-  unsigned char *old = c->alloc ? c->data : NULL;
-  if (str == NULL) {
-    c->len = 0;
-    c->data = NULL;
-    c->alloc = 0;
-  } else {
-    c->len = (bufsize_t)strlen(str);
-    c->data = (unsigned char *)mem->calloc(c->len + 1, 1);
-    c->alloc = 1;
-    memcpy(c->data, str, c->len + 1);
-  }
-  if (old != NULL) {
-    mem->free(old);
-  }
-}
-
 static CMARK_INLINE cmark_chunk cmark_chunk_literal(const char *data) {
   bufsize_t len = data ? (bufsize_t)strlen(data) : 0;
-  cmark_chunk c = {(unsigned char *)data, len, 0};
+  cmark_chunk c = {(unsigned char *)data, len};
   return c;
 }
 
 static CMARK_INLINE cmark_chunk cmark_chunk_dup(const cmark_chunk *ch,
                                                 bufsize_t pos, bufsize_t len) {
-  cmark_chunk c = {ch->data + pos, len, 0};
-  return c;
-}
-
-static CMARK_INLINE cmark_chunk cmark_chunk_buf_detach(cmark_strbuf *buf) {
-  cmark_chunk c;
-
-  c.len = buf->size;
-  c.data = cmark_strbuf_detach(buf);
-  c.alloc = 1;
-
+  cmark_chunk c = {ch->data + pos, len};
   return c;
 }
 
diff --git a/src/commonmark.c b/src/commonmark.c
@@ -146,7 +146,7 @@ static bool is_autolink(cmark_node *node) {
   if (strcmp((const char *)url, "mailto:") == 0) {
     url += 7;
   }
-  return strcmp((const char *)url, (char *)link_text->as.literal.data) == 0;
+  return strcmp((const char *)url, (char *)link_text->data) == 0;
 }
 
 // if node is a block node, returns node.
diff --git a/src/html.c b/src/html.c
@@ -61,7 +61,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
     case CMARK_NODE_TEXT:
     case CMARK_NODE_CODE:
     case CMARK_NODE_HTML_INLINE:
-      escape_html(html, node->as.literal.data, node->as.literal.len);
+      escape_html(html, node->data, node->len);
       break;
 
     case CMARK_NODE_LINEBREAK:
@@ -164,8 +164,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
       cmark_strbuf_puts(html, "\">");
     }
 
-    escape_html(html, node->as.code.literal,
-                strlen((char *)node->as.code.literal));
+    escape_html(html, node->data, node->len);
     cmark_strbuf_puts(html, "</code></pre>\n");
     break;
 
@@ -174,7 +173,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
     if (!(options & CMARK_OPT_UNSAFE)) {
       cmark_strbuf_puts(html, "<!-- raw HTML omitted -->");
     } else {
-      cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
+      cmark_strbuf_put(html, node->data, node->len);
     }
     cr(html);
     break;
@@ -218,7 +217,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
     break;
 
   case CMARK_NODE_TEXT:
-    escape_html(html, node->as.literal.data, node->as.literal.len);
+    escape_html(html, node->data, node->len);
     break;
 
   case CMARK_NODE_LINEBREAK:
@@ -237,7 +236,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
 
   case CMARK_NODE_CODE:
     cmark_strbuf_puts(html, "<code>");
-    escape_html(html, node->as.literal.data, node->as.literal.len);
+    escape_html(html, node->data, node->len);
     cmark_strbuf_puts(html, "</code>");
     break;
 
@@ -245,7 +244,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
     if (!(options & CMARK_OPT_UNSAFE)) {
       cmark_strbuf_puts(html, "<!-- raw HTML omitted -->");
     } else {
-      cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
+      cmark_strbuf_put(html, node->data, node->len);
     }
     break;
 
@@ -325,7 +324,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
 
 char *cmark_render_html(cmark_node *root, int options) {
   char *result;
-  cmark_strbuf html = CMARK_BUF_INIT(cmark_node_mem(root));
+  cmark_strbuf html = CMARK_BUF_INIT(root->mem);
   cmark_event_type ev_type;
   cmark_node *cur;
   struct render_state state = {&html, NULL};
diff --git a/src/inlines.c b/src/inlines.c
@@ -80,7 +80,7 @@ static bufsize_t subject_find_special_char(subject *subj, int options);
 static CMARK_INLINE cmark_node *make_literal(subject *subj, cmark_node_type t,
                                              int start_column, int end_column) {
   cmark_node *e = (cmark_node *)subj->mem->calloc(1, sizeof(*e));
-  cmark_strbuf_init(subj->mem, &e->content, 0);
+  e->mem = subj->mem;
   e->type = (uint16_t)t;
   e->start_line = e->end_line = subj->line;
   // columns are 1 based.
@@ -92,25 +92,25 @@ static CMARK_INLINE cmark_node *make_literal(subject *subj, cmark_node_type t,
 // Create an inline with no value.
 static CMARK_INLINE cmark_node *make_simple(cmark_mem *mem, cmark_node_type t) {
   cmark_node *e = (cmark_node *)mem->calloc(1, sizeof(*e));
-  cmark_strbuf_init(mem, &e->content, 0);
+  e->mem = mem;
   e->type = t;
   return e;
 }
 
 static cmark_node *make_str(subject *subj, int sc, int ec, cmark_chunk s) {
   cmark_node *e = make_literal(subj, CMARK_NODE_TEXT, sc, ec);
-  e->as.literal.data = (unsigned char *)subj->mem->realloc(NULL, s.len + 1);
-  memcpy(e->as.literal.data, s.data, s.len);
-  e->as.literal.data[s.len] = 0;
-  e->as.literal.len = s.len;
+  e->data = (unsigned char *)subj->mem->realloc(NULL, s.len + 1);
+  memcpy(e->data, s.data, s.len);
+  e->data[s.len] = 0;
+  e->len = s.len;
   return e;
 }
 
 static cmark_node *make_str_from_buf(subject *subj, int sc, int ec,
                                      cmark_strbuf *buf) {
   cmark_node *e = make_literal(subj, CMARK_NODE_TEXT, sc, ec);
-  e->as.literal.len = buf->size;
-  e->as.literal.data = cmark_strbuf_detach(buf);
+  e->len = buf->size;
+  e->data = cmark_strbuf_detach(buf);
   return e;
 }
 
@@ -382,8 +382,8 @@ static cmark_node *handle_backticks(subject *subj, int options) {
 
     cmark_node *node = make_literal(subj, CMARK_NODE_CODE, startpos,
                                     endpos - openticks.len - 1);
-    node->as.literal.len = buf.size;
-    node->as.literal.data = cmark_strbuf_detach(&buf);
+    node->len = buf.size;
+    node->data = cmark_strbuf_detach(&buf);
     adjust_subj_node_newlines(subj, node, endpos - startpos, openticks.len, options);
     return node;
   }
@@ -503,7 +503,7 @@ static void push_delimiter(subject *subj, unsigned char c, bool can_open,
   delim->can_open = can_open;
   delim->can_close = can_close;
   delim->inl_text = inl_text;
-  delim->length = inl_text->as.literal.len;
+  delim->length = inl_text->len;
   delim->previous = subj->last_delim;
   delim->next = NULL;
   if (delim->previous != NULL) {
@@ -709,8 +709,8 @@ static delimiter *S_insert_emph(subject *subj, delimiter *opener,
   bufsize_t use_delims;
   cmark_node *opener_inl = opener->inl_text;
   cmark_node *closer_inl = closer->inl_text;
-  bufsize_t opener_num_chars = opener_inl->as.literal.len;
-  bufsize_t closer_num_chars = closer_inl->as.literal.len;
+  bufsize_t opener_num_chars = opener_inl->len;
+  bufsize_t closer_num_chars = closer_inl->len;
   cmark_node *tmp, *tmpnext, *emph;
 
   // calculate the actual number of characters used from this closer
@@ -719,10 +719,10 @@ static delimiter *S_insert_emph(subject *subj, delimiter *opener,
   // remove used characters from associated inlines.
   opener_num_chars -= use_delims;
   closer_num_chars -= use_delims;
-  opener_inl->as.literal.len = opener_num_chars;
-  opener_inl->as.literal.data[opener_num_chars] = 0;
-  closer_inl->as.literal.len = closer_num_chars;
-  closer_inl->as.literal.data[closer_num_chars] = 0;
+  opener_inl->len = opener_num_chars;
+  opener_inl->data[opener_num_chars] = 0;
+  closer_inl->len = closer_num_chars;
+  closer_inl->data[closer_num_chars] = 0;
 
   // free delimiters between opener and closer
   delim = closer->previous;
@@ -866,15 +866,15 @@ static cmark_node *handle_pointy_brace(subject *subj, int options) {
   // finally, try to match an html tag
   matchlen = scan_html_tag(&subj->input, subj->pos);
   if (matchlen > 0) {
-    unsigned char *src = subj->input.data + subj->pos - 1;
+    const unsigned char *src = subj->input.data + subj->pos - 1;
     bufsize_t len = matchlen + 1;
     subj->pos += matchlen;
     cmark_node *node = make_literal(subj, CMARK_NODE_HTML_INLINE,
                                     subj->pos - matchlen - 1, subj->pos - 1);
-    node->as.literal.data = (unsigned char *)subj->mem->realloc(NULL, len + 1);
-    memcpy(node->as.literal.data, src, len);
-    node->as.literal.data[len] = 0;
-    node->as.literal.len = len;
+    node->data = (unsigned char *)subj->mem->realloc(NULL, len + 1);
+    memcpy(node->data, src, len);
+    node->data[len] = 0;
+    node->len = len;
     adjust_subj_node_newlines(subj, node, matchlen, 1, options);
     return node;
   }
@@ -963,7 +963,7 @@ static bufsize_t manual_scan_link_url_2(cmark_chunk *input, bufsize_t offset,
     return -1;
 
   {
-    cmark_chunk result = {input->data + offset, i - offset, 0};
+    cmark_chunk result = {input->data + offset, i - offset};
     *output = result;
   }
   return i - offset;
@@ -994,7 +994,7 @@ static bufsize_t manual_scan_link_url(cmark_chunk *input, bufsize_t offset,
     return -1;
 
   {
-    cmark_chunk result = {input->data + offset + 1, i - 2 - offset, 0};
+    cmark_chunk result = {input->data + offset + 1, i - 2 - offset};
     *output = result;
   }
   return i - offset;
@@ -1061,8 +1061,8 @@ static cmark_node *handle_close_bracket(subject *subj) {
           cmark_chunk_dup(&subj->input, starttitle, endtitle - starttitle);
       url = cmark_clean_url(subj->mem, &url_chunk);
       title = cmark_clean_title(subj->mem, &title_chunk);
-      cmark_chunk_free(subj->mem, &url_chunk);
-      cmark_chunk_free(subj->mem, &title_chunk);
+      cmark_chunk_free(&url_chunk);
+      cmark_chunk_free(&title_chunk);
       goto match;
 
     } else {
@@ -1082,7 +1082,7 @@ static cmark_node *handle_close_bracket(subject *subj) {
   }
 
   if ((!found_label || raw_label.len == 0) && !opener->bracket_after) {
-    cmark_chunk_free(subj->mem, &raw_label);
+    cmark_chunk_free(&raw_label);
     raw_label = cmark_chunk_dup(&subj->input, opener->position,
                                 initial_pos - opener->position - 1);
     found_label = true;
@@ -1090,7 +1090,7 @@ static cmark_node *handle_close_bracket(subject *subj) {
 
   if (found_label) {
     ref = cmark_reference_lookup(subj->refmap, &raw_label);
-    cmark_chunk_free(subj->mem, &raw_label);
+    cmark_chunk_free(&raw_label);
   }
 
   if (ref != NULL) { // found
@@ -1294,10 +1294,10 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
 }
 
 // Parse inlines from parent's string_content, adding as children of parent.
-extern void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent,
-                                cmark_reference_map *refmap, int options) {
+void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent,
+                         cmark_reference_map *refmap, int options) {
   subject subj;
-  cmark_chunk content = {parent->content.ptr, parent->content.size, 0};
+  cmark_chunk content = {parent->data, parent->len};
   subject_from_buf(mem, parent->start_line, parent->start_column - 1 + parent->internal_offset, &subj, &content, refmap);
   cmark_chunk_rtrim(&subj.input);
 
diff --git a/src/iterator.c b/src/iterator.c
@@ -16,7 +16,7 @@ cmark_iter *cmark_iter_new(cmark_node *root) {
   if (root == NULL) {
     return NULL;
   }
-  cmark_mem *mem = root->content.mem;
+  cmark_mem *mem = root->mem;
   cmark_iter *iter = (cmark_iter *)mem->calloc(1, sizeof(cmark_iter));
   iter->mem = mem;
   iter->root = root;
@@ -101,19 +101,19 @@ void cmark_consolidate_text_nodes(cmark_node *root) {
     if (ev_type == CMARK_EVENT_ENTER && cur->type == CMARK_NODE_TEXT &&
         cur->next && cur->next->type == CMARK_NODE_TEXT) {
       cmark_strbuf_clear(&buf);
-      cmark_strbuf_put(&buf, cur->as.literal.data, cur->as.literal.len);
+      cmark_strbuf_put(&buf, cur->data, cur->len);
       tmp = cur->next;
       while (tmp && tmp->type == CMARK_NODE_TEXT) {
         cmark_iter_next(iter); // advance pointer
-        cmark_strbuf_put(&buf, tmp->as.literal.data, tmp->as.literal.len);
+        cmark_strbuf_put(&buf, tmp->data, tmp->len);
         cur->end_column = tmp->end_column;
         next = tmp->next;
         cmark_node_free(tmp);
         tmp = next;
       }
-      iter->mem->free(cur->as.literal.data);
-      cur->as.literal.len = buf.size;
-      cur->as.literal.data = cmark_strbuf_detach(&buf);
+      iter->mem->free(cur->data);
+      cur->len = buf.size;
+      cur->data = cmark_strbuf_detach(&buf);
     }
   }
 
diff --git a/src/latex.c b/src/latex.c
@@ -190,9 +190,9 @@ static link_type get_link_type(cmark_node *node) {
       realurllen -= 7;
       isemail = true;
     }
-    if (realurllen == link_text->as.literal.len &&
-        strncmp(realurl, (char *)link_text->as.literal.data,
-                link_text->as.literal.len) == 0) {
+    if (realurllen == link_text->len &&
+        strncmp(realurl, (char *)link_text->data,
+                link_text->len) == 0) {
       if (isemail) {
         return EMAIL_AUTOLINK;
       } else {
diff --git a/src/main.c b/src/main.c
@@ -69,7 +69,7 @@ static void print_document(cmark_node *document, writer_format writer,
     exit(1);
   }
   printf("%s", result);
-  cmark_node_mem(document)->free(result);
+  document->mem->free(result);
 }
 
 int main(int argc, char *argv[]) {
diff --git a/src/node.c b/src/node.c
@@ -6,8 +6,6 @@
 
 static void S_node_unlink(cmark_node *node);
 
-#define NODE_MEM(node) cmark_node_mem(node)
-
 static CMARK_INLINE bool S_is_block(cmark_node *node) {
   if (node == NULL) {
     return false;
@@ -74,7 +72,7 @@ static bool S_can_contain(cmark_node *node, cmark_node *child) {
 
 cmark_node *cmark_node_new_with_mem(cmark_node_type type, cmark_mem *mem) {
   cmark_node *node = (cmark_node *)mem->calloc(1, sizeof(*node));
-  cmark_strbuf_init(mem, &node->content, 0);
+  node->mem = mem;
   node->type = (uint16_t)type;
 
   switch (node->type) {
@@ -104,29 +102,29 @@ cmark_node *cmark_node_new(cmark_node_type type) {
 
 // Free a cmark_node list and any children.
 static void S_free_nodes(cmark_node *e) {
+  cmark_mem *mem = e->mem;
   cmark_node *next;
   while (e != NULL) {
-    cmark_strbuf_free(&e->content);
     switch (e->type) {
     case CMARK_NODE_CODE_BLOCK:
-      NODE_MEM(e)->free(e->as.code.info);
-      NODE_MEM(e)->free(e->as.code.literal);
+      mem->free(e->data);
+      mem->free(e->as.code.info);
       break;
     case CMARK_NODE_TEXT:
     case CMARK_NODE_HTML_INLINE:
     case CMARK_NODE_CODE:
     case CMARK_NODE_HTML_BLOCK:
-      NODE_MEM(e)->free(e->as.literal.data);
+      mem->free(e->data);
       break;
     case CMARK_NODE_LINK:
     case CMARK_NODE_IMAGE:
-      NODE_MEM(e)->free(e->as.link.url);
-      NODE_MEM(e)->free(e->as.link.title);
+      mem->free(e->as.link.url);
+      mem->free(e->as.link.title);
       break;
     case CMARK_NODE_CUSTOM_BLOCK:
     case CMARK_NODE_CUSTOM_INLINE:
-      NODE_MEM(e)->free(e->as.custom.on_enter);
-      NODE_MEM(e)->free(e->as.custom.on_exit);
+      mem->free(e->as.custom.on_enter);
+      mem->free(e->as.custom.on_exit);
       break;
     default:
       break;
@@ -137,7 +135,7 @@ static void S_free_nodes(cmark_node *e) {
       e->next = e->first_child;
     }
     next = e->next;
-    NODE_MEM(e)->free(e);
+    mem->free(e);
     e = next;
   }
 }
@@ -295,10 +293,8 @@ const char *cmark_node_get_literal(cmark_node *node) {
   case CMARK_NODE_TEXT:
   case CMARK_NODE_HTML_INLINE:
   case CMARK_NODE_CODE:
-    return node->as.literal.data ? (char *)node->as.literal.data : "";
-
   case CMARK_NODE_CODE_BLOCK:
-    return (char *)node->as.code.literal;
+    return node->data ? (char *)node->data : "";
 
   default:
     break;
@@ -317,12 +313,8 @@ int cmark_node_set_literal(cmark_node *node, const char *content) {
   case CMARK_NODE_TEXT:
   case CMARK_NODE_HTML_INLINE:
   case CMARK_NODE_CODE:
-    node->as.literal.len = cmark_set_cstr(NODE_MEM(node),
-                                          &node->as.literal.data, content);
-    return 1;
-
   case CMARK_NODE_CODE_BLOCK:
-    cmark_set_cstr(NODE_MEM(node), &node->as.code.literal, content);
+    node->len = cmark_set_cstr(node->mem, &node->data, content);
     return 1;
 
   default:
@@ -491,7 +483,7 @@ int cmark_node_set_fence_info(cmark_node *node, const char *info) {
   }
 
   if (node->type == CMARK_NODE_CODE_BLOCK) {
-    cmark_set_cstr(NODE_MEM(node), &node->as.code.info, info);
+    cmark_set_cstr(node->mem, &node->as.code.info, info);
     return 1;
   } else {
     return 0;
@@ -522,7 +514,7 @@ int cmark_node_set_url(cmark_node *node, const char *url) {
   switch (node->type) {
   case CMARK_NODE_LINK:
   case CMARK_NODE_IMAGE:
-    cmark_set_cstr(NODE_MEM(node), &node->as.link.url, url);
+    cmark_set_cstr(node->mem, &node->as.link.url, url);
     return 1;
   default:
     break;
@@ -555,7 +547,7 @@ int cmark_node_set_title(cmark_node *node, const char *title) {
   switch (node->type) {
   case CMARK_NODE_LINK:
   case CMARK_NODE_IMAGE:
-    cmark_set_cstr(NODE_MEM(node), &node->as.link.title, title);
+    cmark_set_cstr(node->mem, &node->as.link.title, title);
     return 1;
   default:
     break;
@@ -588,7 +580,7 @@ int cmark_node_set_on_enter(cmark_node *node, const char *on_enter) {
   switch (node->type) {
   case CMARK_NODE_CUSTOM_INLINE:
   case CMARK_NODE_CUSTOM_BLOCK:
-    cmark_set_cstr(NODE_MEM(node), &node->as.custom.on_enter, on_enter);
+    cmark_set_cstr(node->mem, &node->as.custom.on_enter, on_enter);
     return 1;
   default:
     break;
@@ -621,7 +613,7 @@ int cmark_node_set_on_exit(cmark_node *node, const char *on_exit) {
   switch (node->type) {
   case CMARK_NODE_CUSTOM_INLINE:
   case CMARK_NODE_CUSTOM_BLOCK:
-    cmark_set_cstr(NODE_MEM(node), &node->as.custom.on_exit, on_exit);
+    cmark_set_cstr(node->mem, &node->as.custom.on_exit, on_exit);
     return 1;
   default:
     break;
diff --git a/src/node.h b/src/node.h
@@ -8,15 +8,11 @@ extern "C" {
 #include <stdio.h>
 #include <stdint.h>
 
+#include "config.h"
 #include "cmark.h"
 #include "buffer.h"
 
 typedef struct {
-  unsigned char *data;
-  bufsize_t len;
-} cmark_literal;
-
-typedef struct {
   int marker_offset;
   int padding;
   int start;
@@ -28,7 +24,6 @@ typedef struct {
 
 typedef struct {
   unsigned char *info;
-  unsigned char *literal;
   uint8_t fence_length;
   uint8_t fence_offset;
   unsigned char fence_char;
@@ -57,7 +52,7 @@ enum cmark_node__internal_flags {
 };
 
 struct cmark_node {
-  cmark_strbuf content;
+  cmark_mem *mem;
 
   struct cmark_node *next;
   struct cmark_node *prev;
@@ -67,6 +62,9 @@ struct cmark_node {
 
   void *user_data;
 
+  unsigned char *data;
+  bufsize_t len;
+
   int start_line;
   int start_column;
   int end_line;
@@ -76,7 +74,6 @@ struct cmark_node {
   uint16_t flags;
 
   union {
-    cmark_literal literal;
     cmark_list list;
     cmark_code code;
     cmark_heading heading;
@@ -86,9 +83,6 @@ struct cmark_node {
   } as;
 };
 
-static CMARK_INLINE cmark_mem *cmark_node_mem(cmark_node *node) {
-  return node->content.mem;
-}
 CMARK_EXPORT int cmark_node_check(cmark_node *node, FILE *out);
 
 #ifdef __cplusplus
diff --git a/src/parser.h b/src/parser.h
@@ -29,6 +29,7 @@ struct cmark_parser {
   cmark_strbuf curline;
   bufsize_t last_line_length;
   cmark_strbuf linebuf;
+  cmark_strbuf content;
   int options;
   bool last_buffer_ended_with_cr;
 };
diff --git a/src/render.c b/src/render.c
@@ -154,7 +154,7 @@ char *cmark_render(cmark_node *root, int options, int width,
                    int (*render_node)(cmark_renderer *renderer,
                                       cmark_node *node,
                                       cmark_event_type ev_type, int options)) {
-  cmark_mem *mem = cmark_node_mem(root);
+  cmark_mem *mem = root->mem;
   cmark_strbuf pref = CMARK_BUF_INIT(mem);
   cmark_strbuf buf = CMARK_BUF_INIT(mem);
   cmark_node *cur;
diff --git a/src/xml.c b/src/xml.c
@@ -61,7 +61,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
     case CMARK_NODE_HTML_BLOCK:
     case CMARK_NODE_HTML_INLINE:
       cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
-      escape_xml(xml, node->as.literal.data, node->as.literal.len);
+      escape_xml(xml, node->data, node->len);
       cmark_strbuf_puts(xml, "</");
       cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
       literal = true;
@@ -101,8 +101,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
         cmark_strbuf_putc(xml, '"');
       }
       cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
-      escape_xml(xml, node->as.code.literal,
-                 strlen((char *)node->as.code.literal));
+      escape_xml(xml, node->data, node->len);
       cmark_strbuf_puts(xml, "</");
       cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
       literal = true;
@@ -153,7 +152,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
 
 char *cmark_render_xml(cmark_node *root, int options) {
   char *result;
-  cmark_strbuf xml = CMARK_BUF_INIT(cmark_node_mem(root));
+  cmark_strbuf xml = CMARK_BUF_INIT(root->mem);
   cmark_event_type ev_type;
   cmark_node *cur;
   struct render_state state = {&xml, 0};