aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn MacFarlane <jgm@berkeley.edu>2014-11-10 06:40:11 -0800
committerJohn MacFarlane <jgm@berkeley.edu>2014-11-10 06:52:06 -0800
commit3bf790198caf5a2e03b0cf3652e3a233e63c20ea (patch)
tree69583dd77705751d4fbc72a07d6a17d487555187
parent8999926893c6d8f5d7f13266509f851c16e1bb84 (diff)
Stack-based link handling in js. All tests pass.
-rw-r--r--js/lib/inlines.js210
1 files changed, 160 insertions, 50 deletions
diff --git a/js/lib/inlines.js b/js/lib/inlines.js
index 275e290..a3355b4 100644
--- a/js/lib/inlines.js
+++ b/js/lib/inlines.js
@@ -374,7 +374,6 @@ var processEmphasis = function(inlines, stack_bottom) {
tempstack = closer.previous;
while (tempstack !== null && tempstack !== opener) {
nextstack = tempstack.previous;
- // TODO add remove_delimiter!
this.removeDelimiter(tempstack);
tempstack = nextstack;
}
@@ -504,23 +503,104 @@ var parseRawLabel = function(s) {
return new InlineParser().parse(s.substr(1, s.length - 2), {});
};
-// Attempt to parse a link. If successful, return the link.
-var parseLink = function(inlines) {
+// Add open bracket to delimiter stack and add a Str to inlines.
+var parseOpenBracket = function(inlines) {
+
+ var startpos = this.pos;
+ this.pos += 1;
+ inlines.push(Str("["));
+
+ // Add entry to stack for this opener
+ this.delimiters = { cc: C_OPEN_BRACKET,
+ numdelims: 1,
+ pos: inlines.length - 1,
+ previous: this.delimiters,
+ next: null,
+ can_open: true,
+ can_close: false,
+ index: startpos };
+ if (this.delimiters.previous != null) {
+ this.delimiters.previous.next = this.delimiters;
+ }
+ return true;
+
+};
+
+// IF next character is [, and ! delimiter to delimiter stack and
+// add a Str to inlines. Otherwise just add a Str.
+var parseBang = function(inlines) {
+
var startpos = this.pos;
- var reflabel;
- var n;
+ this.pos += 1;
+ if (this.peek() === C_OPEN_BRACKET) {
+ this.pos += 1;
+ inlines.push(Str("!["));
+
+ // Add entry to stack for this opener
+ this.delimiters = { cc: C_BANG,
+ numdelims: 1,
+ pos: inlines.length - 1,
+ previous: this.delimiters,
+ next: null,
+ can_open: true,
+ can_close: false,
+ index: startpos + 1 };
+ if (this.delimiters.previous != null) {
+ this.delimiters.previous.next = this.delimiters;
+ }
+ } else {
+ inlines.push(Str("!"));
+ }
+ return true;
+};
+
+// Try to match close bracket against an opening in the delimiter
+// stack. Add either a link or image, or a plain [ character,
+// to the inlines stack. If there is a matching delimiter,
+// remove it from the delimiter stack.
+var parseCloseBracket = function(inlines) {
+ var startpos;
+ var is_image;
var dest;
var title;
+ var matched = false;
+ var link_text;
+ var i;
+ var opener, closer_above, tempstack;
+
+ this.pos += 1;
+ startpos = this.pos;
+
+ // look through stack of delimiters for a [ or !
+ opener = this.delimiters;
+ while (opener !== null) {
+ if (opener.cc === C_OPEN_BRACKET || opener.cc === C_BANG) {
+ break;
+ }
+ opener = opener.previous;
+ }
- n = this.parseLinkLabel();
- if (n === 0) {
- return false;
+ if (opener === null) {
+ // no matched opener, just return a literal
+ inlines.push(Str("]"));
+ return true;
}
- var afterlabel = this.pos;
- var rawlabel = this.subject.substr(startpos, n);
- // if we got this far, we've parsed a label.
- // Try to parse an explicit link: [label](url "title")
+ // If we got here, open is a potential opener
+ is_image = opener.cc === C_BANG;
+ // instead of copying a slice, we null out the
+ // parts of inlines that don't correspond to link_text;
+ // later, we'll collapse them. This is awkward, and could
+ // be simplified if we made inlines a linked list rather than
+ // an array:
+ link_text = inlines.slice(0);
+ for (i = 0; i < opener.pos + 1; i++) {
+ link_text[i] = null;
+ }
+
+ // Check to see if we have a link/image
+
+ // Inline link?
if (this.peek() == C_OPEN_PAREN) {
this.pos++;
if (this.spnl() &&
@@ -531,46 +611,72 @@ var parseLink = function(inlines) {
(title = this.parseLinkTitle() || '') || true) &&
this.spnl() &&
this.match(/^\)/)) {
- inlines.push({ t: 'Link',
- destination: dest,
- title: title,
- label: parseRawLabel(rawlabel) });
- return true;
+ matched = true;
+ }
+ } else {
+
+ // Next, see if there's a link label
+ var savepos = this.pos;
+ this.spnl();
+ var beforelabel = this.pos;
+ n = this.parseLinkLabel();
+ if (n === 0 || n === 2) {
+ // empty or missing second label
+ reflabel = this.subject.slice(opener.index, startpos);
} else {
- this.pos = startpos;
- return false;
+ reflabel = this.subject.slice(beforelabel, beforelabel + n);
+ }
+
+ // lookup rawlabel in refmap
+ var link = this.refmap[normalizeReference(reflabel)];
+ if (link) {
+ dest = link.destination;
+ title = link.title;
+ matched = true;
}
}
- // If we're here, it wasn't an explicit link. Try to parse a reference link.
- // first, see if there's another label
- var savepos = this.pos;
- this.spnl();
- var beforelabel = this.pos;
- n = this.parseLinkLabel();
- if (n == 2) {
- // empty second label
- reflabel = rawlabel;
- } else if (n > 0) {
- reflabel = this.subject.slice(beforelabel, beforelabel + n);
- } else {
- this.pos = savepos;
- reflabel = rawlabel;
- }
- // lookup rawlabel in refmap
- var link = this.refmap[normalizeReference(reflabel)];
- if (link) {
- inlines.push({t: 'Link',
- destination: link.destination,
- title: link.title,
- label: parseRawLabel(rawlabel) });
+
+ if (matched) {
+ this.processEmphasis(link_text, opener.previous);
+
+ // remove the part of inlines that became link_text.
+ // see note above on why we need to do this instead of splice:
+ for (i = opener.pos; i < inlines.length; i++) {
+ inlines[i] = null;
+ }
+
+ // processEmphasis will remove this and later delimiters.
+ // Now we also remove earlier ones of the same kind (so,
+ // no links in links, no images in images).
+ opener = this.delimiters;
+ closer_above = null;
+ while (opener !== null) {
+ if (opener.cc === (is_image ? C_BANG : C_OPEN_BRACKET)) {
+ if (closer_above) {
+ closer_above.previous = opener.previous;
+ } else {
+ this.delimiters = opener.previous;
+ }
+ } else {
+ closer_above = opener;
+ }
+ opener = opener.previous;
+ }
+
+ inlines.push({t: is_image ? 'Image' : 'Link',
+ destination: dest,
+ title: title,
+ label: link_text});
return true;
- } else {
+
+ } else { // no match
+
+ this.removeDelimiter(opener); // remove this opener from stack
this.pos = startpos;
- return false;
+ inlines.push(Str("]"));
+ return true;
}
- // Nothing worked, rewind:
- this.pos = startpos;
- return false;
+
};
// Attempt to parse an entity, return Entity object if successful.
@@ -716,10 +822,13 @@ var parseInline = function(inlines) {
res = this.parseEmphasis(c, inlines);
break;
case C_OPEN_BRACKET:
- res = this.parseLink(inlines);
+ res = this.parseOpenBracket(inlines);
break;
case C_BANG:
- res = this.parseImage(inlines);
+ res = this.parseBang(inlines);
+ break;
+ case C_CLOSE_BRACKET:
+ res = this.parseCloseBracket(inlines);
break;
case C_LESSTHAN:
res = this.parseAutolink(inlines) || this.parseHtmlTag(inlines);
@@ -773,11 +882,12 @@ function InlineParser(){
parseLinkTitle: parseLinkTitle,
parseLinkDestination: parseLinkDestination,
parseLinkLabel: parseLinkLabel,
- parseLink: parseLink,
+ parseOpenBracket: parseOpenBracket,
+ parseCloseBracket: parseCloseBracket,
+ parseBang: parseBang,
parseEntity: parseEntity,
parseString: parseString,
parseNewline: parseNewline,
- parseImage: parseImage,
parseReference: parseReference,
parseInline: parseInline,
processEmphasis: processEmphasis,