Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// Add two functions `can_contain` and `can_be_contained` to the
// node type metaobject. (This is just an example. It's possible
// to split the work between these two functions differently.)
struct cmark_ext_node_type {
cmark_syntax_extension *extension;
char *name;
void (*free)(cmark_node *node);
// Used to check document structure.
int (*can_contain)(cmark_node *node, cmark_node *child);
// Note that this function has a node type parameter.
// `parent_type` must not be `CMARK_NODE_EXT`.
int (*can_be_contained)(cmark_node *node, cmark_node_type parent_type);
};
// Expose `can_be_contained`.
int
cmark_node_can_be_contained(cmark_node *node, cmark_node_type parent_type) {
if (node->type == CMARK_NODE_EXT) {
return node->as.ext.type->can_be_contained(node, parent_type);
}
else {
// This is only needed for extension nodes, but it could do
// something useful for core nodes, too.
...
}
}
// Now cmark's internal `S_can_contain` function can handle extension
// nodes like this:
static bool
S_can_contain(cmark_node *node, cmark_node *child) {
if (node->type == CMARK_NODE_EXT) {
return node->as.ext.type->can_contain(node, child);
}
if (child->type == CMARK_NODE_EXT) {
return child->as.ext.type->can_be_contained(child, node->type);
}
// Both `node` and `child` aren't extension nodes.
// Continue as usual.
...
}
// A "table" node would implement these functions like this.
static int
S_table_can_contain(cmark_node *table, cmark_node *child) {
// This is just an example, but I think it's a good idea
// to use something similar to XML's namespace URIs to
// identify extension nodes.
const char *child_type_uri = child_get_node_type_string(child);
// Only allow table rows.
if (strcmp(child_type_uri, "http://myext.com/table-row") == 0) {
return 1;
}
else {
return 0;
}
}
static int
S_table_can_be_contained(cmark_node *table, cmark_node_type parent_type) {
return parent_type == CMARK_NODE_DOCUMENT
|| parent_type == CMARK_NODE_BLOCKQUOTE
|| parent_type == CMARK_NODE_LIST
|| ...;
}
// A "table cell" node would implement these functions like this.
static int
S_table_cell_can_contain(cmark_node *cell, cmark_node *child) {
cmark_node_type child_type = cmark_node_get_type(child);
if (child_type == CMARK_NODE_EXT) {
// Two extension nodes.
//
// HERE'S THE TRICK: `can_contain` calls `can_be_contained`
// with a standard node type. This means that table cells
// can contain the same node types as documents, i. e. all
// top-level block types.
//
// This makes it possible to handle nodes from separate
// extensions that don't know about each other.
return cmark_node_can_be_contained(child, CMARK_NODE_DOCUMENT);
}
return child_type == CMARK_NODE_PARAGRAPH
|| child_type == CMARK_NODE_BLOCK_QUOTE
|| child_type == CMARK_NODE_LIST
|| ...;
}
static int
S_table_cell_can_be_contained(cmark_node *cell, cmark_node_type parent_type) {
// Table cells can't be contained in non-extension nodes.
return 0;
}
// To understand how this works, this is the call chain when testing
// whether a table cell can contain a table.
//
// S_can_contain(table_cell, table);
// S_table_call_can_contain(table_cell, table);
// S_table_can_be_contained(table, CMARK_NODE_DOCUMENT);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment