From 014d2d0699d8875e766afcf01580c4a2ea093131 Mon Sep 17 00:00:00 2001 From: John MacFarlane Date: Sat, 8 Nov 2014 22:43:39 -0800 Subject: 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. --- src/inlines.c | 267 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 159 insertions(+), 108 deletions(-) (limited to 'src') 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("!")); } -- cgit v1.2.3