Skip to content

Commit

Permalink
Keep a count of the number of open blocks.
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinbackhouse committed Mar 5, 2023
1 parent c32ef78 commit c72487d
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 6 deletions.
6 changes: 6 additions & 0 deletions extensions/table.c
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,18 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
}
}

assert(cmark_node_get_type(parent_container) == CMARK_NODE_PARAGRAPH);
if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) {
free_table_row(parser->mem, header_row);
free_table_row(parser->mem, marker_row);
return parent_container;
}

// Update the node counts after parent_container changed type.
assert(parent_container->next == NULL);
decr_open_block_count(parser, CMARK_NODE_PARAGRAPH);
incr_open_block_count(parser, CMARK_NODE_TABLE);

if (header_row->paragraph_offset) {
try_inserting_table_header_paragraph(parser, parent_container, (unsigned char *)parent_string,
header_row->paragraph_offset);
Expand Down
43 changes: 41 additions & 2 deletions src/blocks.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
static void S_process_line(cmark_parser *parser, const unsigned char *buffer,
bufsize_t bytes);

static void subtract_open_block_counts(cmark_parser *parser, cmark_node *node) {
do {
decr_open_block_count(parser, S_type(node));
node->flags &= ~CMARK_NODE__OPEN_BLOCK;
node = node->last_child;
} while (node);
}

static void add_open_block_counts(cmark_parser *parser, cmark_node *node) {
do {
incr_open_block_count(parser, S_type(node));
node->flags |= CMARK_NODE__OPEN_BLOCK;
node = node->last_child;
} while (node);
}

static cmark_node *make_block(cmark_mem *mem, cmark_node_type tag,
int start_line, int start_column) {
cmark_node *e;
Expand Down Expand Up @@ -129,6 +145,7 @@ static void cmark_parser_reset(cmark_parser *parser) {
parser->refmap = cmark_reference_map_new(parser->mem);
parser->root = document;
parser->current = document;
add_open_block_counts(parser, document);

parser->syntax_extensions = saved_exts;
parser->inline_syntax_extensions = saved_inline_exts;
Expand Down Expand Up @@ -310,6 +327,12 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
has_content = resolve_reference_link_definitions(parser, b);
if (!has_content) {
// remove blank node (former reference def)
if (b->flags & CMARK_NODE__OPEN_BLOCK) {
decr_open_block_count(parser, S_type(b));
if (b->prev) {
add_open_block_counts(parser, b->prev);
}
}
cmark_node_free(b);
}
break;
Expand Down Expand Up @@ -382,6 +405,15 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
return parent;
}

// Recalculates the number of open blocks. Returns true if it matches what's currently stored
// in parser. (Used to check that the counts in parser, which are updated incrementally, are
// correct.)
bool check_open_block_counts(cmark_parser *parser) {
cmark_parser tmp_parser = {0}; // Only used for its open_block_counts field.
add_open_block_counts(&tmp_parser, parser->root);
return memcmp(tmp_parser.open_block_counts, parser->open_block_counts, sizeof(parser->open_block_counts)) == 0;
}

// Add a node as child of another. Return pointer to child.
static cmark_node *add_child(cmark_parser *parser, cmark_node *parent,
cmark_node_type block_type, int start_column) {
Expand All @@ -400,11 +432,14 @@ static cmark_node *add_child(cmark_parser *parser, cmark_node *parent,
if (parent->last_child) {
parent->last_child->next = child;
child->prev = parent->last_child;
subtract_open_block_counts(parser, parent->last_child);
} else {
parent->first_child = child;
child->prev = NULL;
}
parent->last_child = child;
add_open_block_counts(parser, child);

return child;
}

Expand Down Expand Up @@ -1048,6 +1083,8 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input,
cmark_node *container = parser->root;
cmark_node_type cont_type;

assert(check_open_block_counts(parser));

while (S_last_child_is_open(container)) {
container = container->last_child;
cont_type = S_type(container);
Expand Down Expand Up @@ -1193,8 +1230,9 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
has_content = resolve_reference_link_definitions(parser, *container);

if (has_content) {

(*container)->type = (uint16_t)CMARK_NODE_HEADING;
cmark_node_set_type(*container, CMARK_NODE_HEADING);
decr_open_block_count(parser, CMARK_NODE_PARAGRAPH);
incr_open_block_count(parser, CMARK_NODE_HEADING);
(*container)->as.heading.level = lev;
(*container)->as.heading.setext = true;
S_advance_offset(parser, input, input->len - 1 - parser->offset, false);
Expand Down Expand Up @@ -1478,6 +1516,7 @@ static void S_process_line(cmark_parser *parser, const unsigned char *buffer,

parser->line_number++;

assert(parser->current->next == NULL);
last_matched_container = check_open_blocks(parser, &input, &all_matched);

if (!last_matched_container)
Expand Down
10 changes: 10 additions & 0 deletions src/cmark-gfm.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ char *cmark_markdown_to_html(const char *text, size_t len, int options);
#define CMARK_NODE_TYPE_MASK (0xc000)
#define CMARK_NODE_VALUE_MASK (0x3fff)

/**
* This is the maximum number of block types (CMARK_NODE_DOCUMENT,
* CMARK_NODE_HEADING, ...). It needs to be bigger than the number of
* hardcoded block types (below) to allow for extensions (see
* cmark_syntax_extension_add_node). But it also determines the size of the
* open_block_counts array in the cmark_parser struct, so we don't want it
* to be excessively large.
*/
#define CMARK_NODE_TYPE_BLOCK_LIMIT 0x20

typedef enum {
/* Error status */
CMARK_NODE_NONE = 0x0000,
Expand Down
7 changes: 4 additions & 3 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ typedef struct {

enum cmark_node__internal_flags {
CMARK_NODE__OPEN = (1 << 0),
CMARK_NODE__LAST_LINE_BLANK = (1 << 1),
CMARK_NODE__LAST_LINE_CHECKED = (1 << 2),
CMARK_NODE__OPEN_BLOCK = (1 << 1),
CMARK_NODE__LAST_LINE_BLANK = (1 << 2),
CMARK_NODE__LAST_LINE_CHECKED = (1 << 3),

// Extensions can register custom flags by calling `cmark_register_node_flag`.
// This is the starting value for the custom flags.
CMARK_NODE__REGISTER_FIRST = (1 << 3),
CMARK_NODE__REGISTER_FIRST = (1 << 4),
};

typedef uint16_t cmark_node_internal_flags;
Expand Down
29 changes: 29 additions & 0 deletions src/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,37 @@ struct cmark_parser {
cmark_llist *syntax_extensions;
cmark_llist *inline_syntax_extensions;
cmark_ispunct_func backslash_ispunct;

/**
* The "open" blocks are the blocks visited by the loop in
* check_open_blocks (blocks.c). I.e. the blocks in this list:
*
* parser->root->last_child->...->last_child
*
* open_block_counts is used to keep track of how many of each type of
* node are currently in the open blocks list. Knowing these counts can
* sometimes help to end the loop in check_open_blocks early, improving
* efficiency.
*
* The count is stored at this offset: type - CMARK_NODE_TYPE_BLOCK - 1
* For example, CMARK_NODE_LIST (0x8003) is stored at offset 2.
*/
size_t open_block_counts[CMARK_NODE_TYPE_BLOCK_LIMIT];
};

static CMARK_INLINE void incr_open_block_count(cmark_parser *parser, cmark_node_type type) {
assert(type > CMARK_NODE_TYPE_BLOCK);
assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT);
parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]++;
}

static CMARK_INLINE void decr_open_block_count(cmark_parser *parser, cmark_node_type type) {
assert(type > CMARK_NODE_TYPE_BLOCK);
assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT);
assert(parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1] > 0);
parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]--;
}

#ifdef __cplusplus
}
#endif
Expand Down
5 changes: 4 additions & 1 deletion src/syntax_extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ cmark_syntax_extension *cmark_syntax_extension_new(const char *name) {
cmark_node_type cmark_syntax_extension_add_node(int is_inline) {
cmark_node_type *ref = !is_inline ? &CMARK_NODE_LAST_BLOCK : &CMARK_NODE_LAST_INLINE;

if ((*ref & CMARK_NODE_VALUE_MASK) == CMARK_NODE_VALUE_MASK) {
if ((*ref & CMARK_NODE_VALUE_MASK) >= CMARK_NODE_TYPE_BLOCK_LIMIT) {
// This assertion will fail if you try to register more extensions than
// are currently allowed by CMARK_NODE_TYPE_BLOCK_MAXNUM. Try increasing
// the limit.
assert(false);
return (cmark_node_type) 0;
}
Expand Down

0 comments on commit c72487d

Please sign in to comment.