', start: 8, end: 13},
+ * // {name: 'match', value: '
an
', start: 13, end: 27},
+ * // {name: 'right', value: '
', start: 27, end: 33},
+ * // {name: 'between', value: ' example', start: 33, end: 41}
+ * // ]
+ *
+ * // Omitting unneeded parts with null valueNames, and using escapeChar
+ * str = '...{1}\\{{function(x,y){return y+x;}}';
+ * XRegExp.matchRecursive(str, '{', '}', 'g', {
+ * valueNames: ['literal', null, 'value', null],
+ * escapeChar: '\\'
+ * });
+ * // -> [
+ * // {name: 'literal', value: '...', start: 0, end: 3},
+ * // {name: 'value', value: '1', start: 4, end: 5},
+ * // {name: 'literal', value: '\\{', start: 6, end: 8},
+ * // {name: 'value', value: 'function(x,y){return y+x;}', start: 9, end: 35}
+ * // ]
+ *
+ * // Sticky mode via flag y
+ * str = '<1><<<2>>><3>4<5>';
+ * XRegExp.matchRecursive(str, '<', '>', 'gy');
+ * // -> ['1', '<<2>>', '3']
+ */
+ XRegExp.matchRecursive = function (str, left, right, flags, options) {
+ flags = flags || "";
+ options = options || {};
+ var global = flags.indexOf("g") > -1,
+ sticky = flags.indexOf("y") > -1,
+ basicFlags = flags.replace(/y/g, ""), // Flag y controlled internally
+ escapeChar = options.escapeChar,
+ vN = options.valueNames,
+ output = [],
+ openTokens = 0,
+ delimStart = 0,
+ delimEnd = 0,
+ lastOuterEnd = 0,
+ outerStart,
+ innerStart,
+ leftMatch,
+ rightMatch,
+ esc;
+ left = XRegExp(left, basicFlags);
+ right = XRegExp(right, basicFlags);
+
+ if (escapeChar) {
+ if (escapeChar.length > 1) {
+ throw new SyntaxError("can't use more than one escape character");
+ }
+ escapeChar = XRegExp.escape(escapeChar);
+ // Using XRegExp.union safely rewrites backreferences in `left` and `right`
+ esc = new RegExp(
+ "(?:" + escapeChar + "[\\S\\s]|(?:(?!" + XRegExp.union([left, right]).source + ")[^" + escapeChar + "])+)+",
+ flags.replace(/[^im]+/g, "") // Flags gy not needed here; flags nsx handled by XRegExp
+ );
+ }
+
+ while (true) {
+ // If using an escape character, advance to the delimiter's next starting position,
+ // skipping any escaped characters in between
+ if (escapeChar) {
+ delimEnd += (XRegExp.exec(str, esc, delimEnd, "sticky") || [""])[0].length;
+ }
+ leftMatch = XRegExp.exec(str, left, delimEnd);
+ rightMatch = XRegExp.exec(str, right, delimEnd);
+ // Keep the leftmost match only
+ if (leftMatch && rightMatch) {
+ if (leftMatch.index <= rightMatch.index) {
+ rightMatch = null;
+ } else {
+ leftMatch = null;
+ }
+ }
+ /* Paths (LM:leftMatch, RM:rightMatch, OT:openTokens):
+ LM | RM | OT | Result
+ 1 | 0 | 1 | loop
+ 1 | 0 | 0 | loop
+ 0 | 1 | 1 | loop
+ 0 | 1 | 0 | throw
+ 0 | 0 | 1 | throw
+ 0 | 0 | 0 | break
+ * Doesn't include the sticky mode special case
+ * Loop ends after the first completed match if `!global` */
+ if (leftMatch || rightMatch) {
+ delimStart = (leftMatch || rightMatch).index;
+ delimEnd = delimStart + (leftMatch || rightMatch)[0].length;
+ } else if (!openTokens) {
+ break;
+ }
+ if (sticky && !openTokens && delimStart > lastOuterEnd) {
+ break;
+ }
+ if (leftMatch) {
+ if (!openTokens) {
+ outerStart = delimStart;
+ innerStart = delimEnd;
+ }
+ ++openTokens;
+ } else if (rightMatch && openTokens) {
+ if (!--openTokens) {
+ if (vN) {
+ if (vN[0] && outerStart > lastOuterEnd) {
+ output.push(row(vN[0], str.slice(lastOuterEnd, outerStart), lastOuterEnd, outerStart));
+ }
+ if (vN[1]) {
+ output.push(row(vN[1], str.slice(outerStart, innerStart), outerStart, innerStart));
+ }
+ if (vN[2]) {
+ output.push(row(vN[2], str.slice(innerStart, delimStart), innerStart, delimStart));
+ }
+ if (vN[3]) {
+ output.push(row(vN[3], str.slice(delimStart, delimEnd), delimStart, delimEnd));
+ }
+ } else {
+ output.push(str.slice(innerStart, delimStart));
+ }
+ lastOuterEnd = delimEnd;
+ if (!global) {
+ break;
+ }
+ }
+ } else {
+ throw new Error("string contains unbalanced delimiters");
+ }
+ // If the delimiter matched an empty string, avoid an infinite loop
+ if (delimStart === delimEnd) {
+ ++delimEnd;
+ }
+ }
+
+ if (global && !sticky && vN && vN[0] && str.length > lastOuterEnd) {
+ output.push(row(vN[0], str.slice(lastOuterEnd), lastOuterEnd, str.length));
+ }
+
+ return output;
+ };
+
+}(XRegExp));
+
+
+/***** build.js *****/
+
+/*!
+ * XRegExp.build v0.1.0
+ * (c) 2012 Steven Levithan