--- /dev/null
+'use strict';
+
+module.exports = factory;
+
+var MERGEABLE_NODES = {
+ text: mergeText,
+ blockquote: mergeBlockquote
+};
+
+/* Check whether a node is mergeable with adjacent nodes. */
+function mergeable(node) {
+ var start;
+ var end;
+
+ if (node.type !== 'text' || !node.position) {
+ return true;
+ }
+
+ start = node.position.start;
+ end = node.position.end;
+
+ /* Only merge nodes which occupy the same size as their
+ * `value`. */
+ return start.line !== end.line ||
+ end.column - start.column === node.value.length;
+}
+
+/* Merge two text nodes: `node` into `prev`. */
+function mergeText(prev, node) {
+ prev.value += node.value;
+
+ return prev;
+}
+
+/* Merge two blockquotes: `node` into `prev`, unless in
+ * CommonMark mode. */
+function mergeBlockquote(prev, node) {
+ if (this.options.commonmark) {
+ return node;
+ }
+
+ prev.children = prev.children.concat(node.children);
+
+ return prev;
+}
+
+/* Construct a tokenizer. This creates both
+ * `tokenizeInline` and `tokenizeBlock`. */
+function factory(type) {
+ return tokenize;
+
+ /* Tokenizer for a bound `type`. */
+ function tokenize(value, location) {
+ var self = this;
+ var offset = self.offset;
+ var tokens = [];
+ var methods = self[type + 'Methods'];
+ var tokenizers = self[type + 'Tokenizers'];
+ var line = location.line;
+ var column = location.column;
+ var index;
+ var length;
+ var method;
+ var name;
+ var matched;
+ var valueLength;
+
+ /* Trim white space only lines. */
+ if (!value) {
+ return tokens;
+ }
+
+ /* Expose on `eat`. */
+ eat.now = now;
+ eat.file = self.file;
+
+ /* Sync initial offset. */
+ updatePosition('');
+
+ /* Iterate over `value`, and iterate over all
+ * tokenizers. When one eats something, re-iterate
+ * with the remaining value. If no tokenizer eats,
+ * something failed (should not happen) and an
+ * exception is thrown. */
+ while (value) {
+ index = -1;
+ length = methods.length;
+ matched = false;
+
+ while (++index < length) {
+ name = methods[index];
+ method = tokenizers[name];
+
+ if (
+ method &&
+ /* istanbul ignore next */ (!method.onlyAtStart || self.atStart) &&
+ (!method.notInList || !self.inList) &&
+ (!method.notInBlock || !self.inBlock) &&
+ (!method.notInLink || !self.inLink)
+ ) {
+ valueLength = value.length;
+
+ method.apply(self, [eat, value]);
+
+ matched = valueLength !== value.length;
+
+ if (matched) {
+ break;
+ }
+ }
+ }
+
+ /* istanbul ignore if */
+ if (!matched) {
+ self.file.fail(new Error('Infinite loop'), eat.now());
+ }
+ }
+
+ self.eof = now();
+
+ return tokens;
+
+ /* Update line, column, and offset based on
+ * `value`. */
+ function updatePosition(subvalue) {
+ var lastIndex = -1;
+ var index = subvalue.indexOf('\n');
+
+ while (index !== -1) {
+ line++;
+ lastIndex = index;
+ index = subvalue.indexOf('\n', index + 1);
+ }
+
+ if (lastIndex === -1) {
+ column += subvalue.length;
+ } else {
+ column = subvalue.length - lastIndex;
+ }
+
+ if (line in offset) {
+ if (lastIndex !== -1) {
+ column += offset[line];
+ } else if (column <= offset[line]) {
+ column = offset[line] + 1;
+ }
+ }
+ }
+
+ /* Get offset. Called before the first character is
+ * eaten to retrieve the range's offsets. */
+ function getOffset() {
+ var indentation = [];
+ var pos = line + 1;
+
+ /* Done. Called when the last character is
+ * eaten to retrieve the range’s offsets. */
+ return function () {
+ var last = line + 1;
+
+ while (pos < last) {
+ indentation.push((offset[pos] || 0) + 1);
+
+ pos++;
+ }
+
+ return indentation;
+ };
+ }
+
+ /* Get the current position. */
+ function now() {
+ var pos = {line: line, column: column};
+
+ pos.offset = self.toOffset(pos);
+
+ return pos;
+ }
+
+ /* Store position information for a node. */
+ function Position(start) {
+ this.start = start;
+ this.end = now();
+ }
+
+ /* Throw when a value is incorrectly eaten.
+ * This shouldn’t happen but will throw on new,
+ * incorrect rules. */
+ function validateEat(subvalue) {
+ /* istanbul ignore if */
+ if (value.substring(0, subvalue.length) !== subvalue) {
+ /* Capture stack-trace. */
+ self.file.fail(
+ new Error(
+ 'Incorrectly eaten value: please report this ' +
+ 'warning on http://git.io/vg5Ft'
+ ),
+ now()
+ );
+ }
+ }
+
+ /* Mark position and patch `node.position`. */
+ function position() {
+ var before = now();
+
+ return update;
+
+ /* Add the position to a node. */
+ function update(node, indent) {
+ var prev = node.position;
+ var start = prev ? prev.start : before;
+ var combined = [];
+ var n = prev && prev.end.line;
+ var l = before.line;
+
+ node.position = new Position(start);
+
+ /* If there was already a `position`, this
+ * node was merged. Fixing `start` wasn’t
+ * hard, but the indent is different.
+ * Especially because some information, the
+ * indent between `n` and `l` wasn’t
+ * tracked. Luckily, that space is
+ * (should be?) empty, so we can safely
+ * check for it now. */
+ if (prev && indent && prev.indent) {
+ combined = prev.indent;
+
+ if (n < l) {
+ while (++n < l) {
+ combined.push((offset[n] || 0) + 1);
+ }
+
+ combined.push(before.column);
+ }
+
+ indent = combined.concat(indent);
+ }
+
+ node.position.indent = indent || [];
+
+ return node;
+ }
+ }
+
+ /* Add `node` to `parent`s children or to `tokens`.
+ * Performs merges where possible. */
+ function add(node, parent) {
+ var children = parent ? parent.children : tokens;
+ var prev = children[children.length - 1];
+
+ if (
+ prev &&
+ node.type === prev.type &&
+ node.type in MERGEABLE_NODES &&
+ mergeable(prev) &&
+ mergeable(node)
+ ) {
+ node = MERGEABLE_NODES[node.type].call(self, prev, node);
+ }
+
+ if (node !== prev) {
+ children.push(node);
+ }
+
+ if (self.atStart && tokens.length !== 0) {
+ self.exitStart();
+ }
+
+ return node;
+ }
+
+ /* Remove `subvalue` from `value`.
+ * `subvalue` must be at the start of `value`. */
+ function eat(subvalue) {
+ var indent = getOffset();
+ var pos = position();
+ var current = now();
+
+ validateEat(subvalue);
+
+ apply.reset = reset;
+ reset.test = test;
+ apply.test = test;
+
+ value = value.substring(subvalue.length);
+
+ updatePosition(subvalue);
+
+ indent = indent();
+
+ return apply;
+
+ /* Add the given arguments, add `position` to
+ * the returned node, and return the node. */
+ function apply(node, parent) {
+ return pos(add(pos(node), parent), indent);
+ }
+
+ /* Functions just like apply, but resets the
+ * content: the line and column are reversed,
+ * and the eaten value is re-added.
+ * This is useful for nodes with a single
+ * type of content, such as lists and tables.
+ * See `apply` above for what parameters are
+ * expected. */
+ function reset() {
+ var node = apply.apply(null, arguments);
+
+ line = current.line;
+ column = current.column;
+ value = subvalue + value;
+
+ return node;
+ }
+
+ /* Test the position, after eating, and reverse
+ * to a not-eaten state. */
+ function test() {
+ var result = pos({});
+
+ line = current.line;
+ column = current.column;
+ value = subvalue + value;
+
+ return result.position;
+ }
+ }
+ }
+}