summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn MacFarlane <jgm@berkeley.edu>2014-11-08 22:43:39 -0800
committerJohn MacFarlane <jgm@berkeley.edu>2014-11-09 13:21:37 -0800
commit014d2d0699d8875e766afcf01580c4a2ea093131 (patch)
tree5d2f7ad813a1f6e78c5403a80d3300e266b69648
parent7c1733bbf56cc99d053e99ecb16356586a4fc61a (diff)
Restored priority of links over emphasis grouping.
Now when we encounter (possibly) closing `*` or `_` delimiters, we simply add them to the delimiters stack. This gets processed by `process_emphasis` either (a) when a link is created (in which case only the inlines created by the link are processed) or at the end of processing a run of inlines.
-rw-r--r--src/inlines.c267
1 files changed, 159 insertions, 108 deletions
diff --git a/src/inlines.c b/src/inlines.c
index 0527d92..a1ecf01 100644
--- a/src/inlines.c
+++ b/src/inlines.c
@@ -11,19 +11,23 @@
#include "inlines.h"
#include "debug.h"
-typedef struct OpenerStack {
- struct OpenerStack *previous;
+
+typedef struct DelimiterStack {
+ struct DelimiterStack *previous;
+ struct DelimiterStack *next;
node_inl *first_inline;
int delim_count;
unsigned char delim_char;
int position;
-} opener_stack;
+ bool can_open;
+ bool can_close;
+} delimiter_stack;
typedef struct Subject {
chunk input;
int pos;
reference_map *refmap;
- opener_stack *openers;
+ delimiter_stack *delimiters;
} subject;
static node_inl *parse_inlines_from_subject(subject* subj);
@@ -182,7 +186,7 @@ static void subject_from_buf(subject *e, strbuf *buffer, reference_map *refmap)
e->input.alloc = 0;
e->pos = 0;
e->refmap = refmap;
- e->openers = NULL;
+ e->delimiters = NULL;
chunk_rtrim(&e->input);
}
@@ -296,30 +300,57 @@ static int scan_delims(subject* subj, unsigned char c, bool * can_open, bool * c
return numdelims;
}
-static void free_openers(subject* subj, opener_stack* istack)
+/*
+static void print_delimiters(subject *subj)
+{
+ delimiter_stack *tempstack;
+ tempstack = subj->delimiters;
+ while (tempstack != NULL) {
+ printf("Item at %p: %d %d %d %d next(%p) prev(%p)\n",
+ tempstack, tempstack->delim_count, tempstack->delim_char,
+ tempstack->can_open, tempstack->can_close,
+ tempstack->next, tempstack->previous);
+ tempstack = tempstack->previous;
+ }
+}
+*/
+
+static void remove_delimiter(subject *subj, delimiter_stack *stack)
{
- opener_stack * tempstack;
- while (subj->openers != istack) {
- tempstack = subj->openers;
- subj->openers = subj->openers->previous;
- free(tempstack);
+ if (stack->previous != NULL) {
+ stack->previous->next = stack->next;
}
+ if (stack->next == NULL) {
+ // top of stack
+ subj->delimiters = stack->previous;
+ } else {
+ stack->next->previous = stack->previous;
+ }
+ free(stack);
}
-static opener_stack * push_opener(subject *subj,
- int numdelims,
- unsigned char c,
- node_inl *inl_text)
+static delimiter_stack * push_delimiter(subject *subj,
+ int numdelims,
+ unsigned char c,
+ bool can_open,
+ bool can_close,
+ node_inl *inl_text)
{
- opener_stack *istack =
- (opener_stack*)malloc(sizeof(opener_stack));
+ delimiter_stack *istack =
+ (delimiter_stack*)malloc(sizeof(delimiter_stack));
if (istack == NULL) {
return NULL;
}
istack->delim_count = numdelims;
istack->delim_char = c;
+ istack->can_open = can_open;
+ istack->can_close = can_close;
istack->first_inline = inl_text;
- istack->previous = subj->openers;
+ istack->previous = subj->delimiters;
+ istack->next = NULL;
+ if (istack->previous != NULL) {
+ istack->previous->next = istack;
+ }
istack->position = subj->pos;
return istack;
}
@@ -328,91 +359,119 @@ static opener_stack * push_opener(subject *subj,
// Assumes the subject has '_' or '*' at the current position.
static node_inl* handle_strong_emph(subject* subj, unsigned char c, node_inl **last)
{
- bool can_open, can_close;
int numdelims;
- int useDelims;
- int openerDelims;
- opener_stack * istack;
- node_inl * inl;
- node_inl * emph;
node_inl * inl_text;
+ bool can_open, can_close;
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->openers;
- while (true)
- {
- if (istack == NULL)
- goto cannotClose;
+ inl_text = make_str(chunk_dup(&subj->input, subj->pos - numdelims, numdelims));
- if (istack->delim_char == c)
- break;
+ if (can_open || can_close) {
+ subj->delimiters = push_delimiter(subj, numdelims, c, can_open, can_close,
+ inl_text);
+ }
- istack = istack->previous;
- }
+ return inl_text;
+}
- // 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;
- }
+static void process_emphasis(subject *subj, delimiter_stack *stack_bottom)
+{
+ delimiter_stack *closer = subj->delimiters;
+ delimiter_stack *opener, *tempstack, *nextstack;
+ int use_delims;
+ node_inl *inl, *tmp, *emph;
+
+ // move back to first relevant delim.
+ while (closer != NULL && closer->previous != stack_bottom) {
+ closer = closer->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;
+ // now move forward, looking for closers, and handling each
+ while (closer != NULL) {
+ if (closer->can_close &&
+ (closer->delim_char == '*' || closer->delim_char == '_')) {
+ // Now look backwards for first matching opener:
+ opener = closer->previous;
+ while (opener != NULL && opener != stack_bottom) {
+ if (opener->delim_char == closer->delim_char &&
+ opener->can_open) {
+ break;
+ }
+ opener = opener->previous;
+ }
+ if (opener != NULL && opener != stack_bottom) {
+ // calculate the actual number of delimeters used from this closer
+ if (closer->delim_count < 3 || opener->delim_count < 3) {
+ use_delims = closer->delim_count <= opener->delim_count ?
+ closer->delim_count : opener->delim_count;
+ } else { // closer and opener both have >= 3 delims
+ use_delims = closer->delim_count % 2 == 0 ? 2 : 1;
}
- 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;
+ inl = opener->first_inline;
- // remove all later openers from stack:
- free_openers(subj, istack);
+ // remove used delimiters from stack elements and associated inlines.
+ opener->delim_count -= use_delims;
+ closer->delim_count -= use_delims;
+ inl->content.literal.len = opener->delim_count;
+ closer->first_inline->content.literal.len = closer->delim_count;
- *last = emph;
+ // free delimiters between opener and closer
+ tempstack = closer->previous;
+ while (tempstack != NULL && tempstack != opener) {
+ nextstack = tempstack->previous;
+ remove_delimiter(subj, tempstack);
+ tempstack = nextstack;
}
- // 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;
+ // create new emph or strong, and splice it in to our inlines
+ // between the opener and closer
+ emph = use_delims == 1 ? make_emph(inl->next) : make_strong(inl->next);
+ emph->next = closer->first_inline;
+ inl->next = emph;
+ tmp = emph->content.inlines;
+ while (tmp->next != NULL && tmp->next != closer->first_inline) {
+ tmp = tmp->next;
+ }
+ tmp->next = NULL;
+
+ // if opener has 0 delims, remove it and its associated inline
+ if (opener->delim_count == 0) {
+ // replace empty opener inline with emph
+ chunk_free(&(inl->content.literal));
+ inl->tag = emph->tag;
+ inl->next = emph->next;
+ inl->content.inlines = emph->content.inlines;
+ free(emph);
+ emph = inl;
+ // remove opener from stack
+ remove_delimiter(subj, opener);
}
- return NULL; // make_str(chunk_literal(""));
- }
-
- cannotClose:
- inl_text = make_str(chunk_dup(&subj->input, subj->pos - numdelims, numdelims));
-
- if (can_open)
- {
- subj->openers = push_opener(subj,
- numdelims,
- c,
- inl_text);
+ // if closer has 0 delims, remove it and its associated inline
+ if (closer->delim_count == 0) {
+ // remove empty closer inline
+ tmp = closer->first_inline;
+ emph->next = tmp->next;
+ tmp->next = NULL;
+ free_inlines(tmp);
+ // remove closer from stack
+ tempstack = closer->next;
+ remove_delimiter(subj, closer);
+ closer = tempstack;
+ }
+ } else {
+ closer = closer->next;
+ }
+ } else {
+ closer = closer->next;
}
-
- return inl_text;
+ }
+ // free all delimiters in stack down to stack_bottom:
+ while (subj->delimiters != stack_bottom) {
+ remove_delimiter(subj, subj->delimiters);
+ }
}
// Parse backslash-escape or just a backslash, returning an inline.
@@ -623,9 +682,9 @@ static node_inl* handle_close_bracket(subject* subj, node_inl **last)
bool is_image = false;
chunk urlchunk, titlechunk;
unsigned char *url, *title;
- opener_stack *ostack = subj->openers;
- opener_stack *closer_above;
- opener_stack *tempstack;
+ delimiter_stack *ostack;
+ delimiter_stack *closer_above;
+ delimiter_stack *tempstack;
node_inl *link_text;
node_inl *inl;
chunk raw_label;
@@ -633,7 +692,8 @@ static node_inl* handle_close_bracket(subject* subj, node_inl **last)
advance(subj); // advance past ]
initial_pos = subj->pos;
- // look through stack of openers for a [ or !
+ // look through stack of delimiters for a [ or !
+ ostack = subj->delimiters;
while (ostack) {
if (ostack->delim_char == '[' || ostack->delim_char == '!') {
break;
@@ -713,19 +773,18 @@ match:
inl->tag = is_image ? INL_IMAGE : INL_LINK;
chunk_free(&inl->content.literal);
inl->content.linkable.label = link_text;
+ process_emphasis(subj, ostack->previous);
inl->content.linkable.url = url;
inl->content.linkable.title = title;
inl->next = NULL;
*last = inl;
- // remove this opener and all later ones:
- free_openers(subj, ostack->previous);
-
- // remove earlier ones of the same kind
+ // process_emphasis will remove this delimiter and all later ones.
+ // Now we also remove earlier ones of the same kind
// (so, no links in links, and no images in images):
// (This code can be removed if we decide to allow links
// inside links and images inside images):
- ostack = subj->openers;
+ ostack = subj->delimiters;
closer_above = NULL;
while (ostack != NULL) {
tempstack = ostack->previous;
@@ -734,7 +793,7 @@ match:
if (closer_above) {
closer_above->previous = tempstack;
} else {
- subj->openers = tempstack;
+ subj->delimiters = tempstack;
}
} else {
closer_above = ostack;
@@ -777,13 +836,7 @@ extern node_inl* parse_inlines_from_subject(subject* subj)
}
}
- opener_stack* istack = subj->openers;
- opener_stack* temp;
- while (istack != NULL) {
- temp = istack->previous;
- free(istack);
- istack = temp;
- }
+ process_emphasis(subj, NULL);
return first;
}
@@ -849,16 +902,14 @@ static int parse_inline(subject* subj, node_inl ** last)
case '<':
new = handle_pointy_brace(subj);
break;
- case '_':
- new = handle_strong_emph(subj, '_', last);
- break;
case '*':
- new = handle_strong_emph(subj, '*', last);
+ case '_':
+ new = handle_strong_emph(subj, c, last);
break;
case '[':
advance(subj);
new = make_str(chunk_literal("["));
- subj->openers = push_opener(subj, 1, '[', new);
+ subj->delimiters = push_delimiter(subj, 1, '[', true, false, new);
break;
case ']':
new = handle_close_bracket(subj, last);
@@ -868,7 +919,7 @@ static int parse_inline(subject* subj, node_inl ** last)
if (peek_char(subj) == '[') {
advance(subj);
new = make_str(chunk_literal("!["));
- subj->openers = push_opener(subj, 1, '!', new);
+ subj->delimiters = push_delimiter(subj, 1, '!', false, true, new);
} else {
new = make_str(chunk_literal("!"));
}