'use strict'; var utils = require('./utils'); module.exports = function(braces, options) { braces.compiler /** * bos */ .set('bos', function() { if (this.output) return; this.ast.queue = isEscaped(this.ast) ? [this.ast.val] : []; this.ast.count = 1; }) /** * Square brackets */ .set('bracket', function(node) { var close = node.close; var open = !node.escaped ? '[' : '\\['; var negated = node.negated; var inner = node.inner; inner = inner.replace(/\\(?=[\\\w]|$)/g, '\\\\'); if (inner === ']-') { inner = '\\]\\-'; } if (negated && inner.indexOf('.') === -1) { inner += '.'; } if (negated && inner.indexOf('/') === -1) { inner += '/'; } var val = open + negated + inner + close; var queue = node.parent.queue; var last = utils.arrayify(queue.pop()); queue.push(utils.join(last, val)); queue.push.apply(queue, []); }) /** * Brace */ .set('brace', function(node) { node.queue = isEscaped(node) ? [node.val] : []; node.count = 1; return this.mapVisit(node.nodes); }) /** * Open */ .set('brace.open', function(node) { node.parent.open = node.val; }) /** * Inner */ .set('text', function(node) { var queue = node.parent.queue; var escaped = node.escaped; var segs = [node.val]; if (node.optimize === false) { options = utils.extend({}, options, {optimize: false}); } if (node.multiplier > 1) { node.parent.count *= node.multiplier; } if (options.quantifiers === true && utils.isQuantifier(node.val)) { escaped = true; } else if (node.val.length > 1) { if (isType(node.parent, 'brace') && !isEscaped(node)) { var expanded = utils.expand(node.val, options); segs = expanded.segs; if (expanded.isOptimized) { node.parent.isOptimized = true; } // if nothing was expanded, we probably have a literal brace if (!segs.length) { var val = (expanded.val || node.val); if (options.unescape !== false) { // unescape unexpanded brace sequence/set separators val = val.replace(/\\([,.])/g, '$1'); // strip quotes val = val.replace(/["'`]/g, ''); } segs = [val]; escaped = true; } } } else if (node.val === ',') { if (options.expand) { node.parent.queue.push(['']); segs = ['']; } else { segs = ['|']; } } else { escaped = true; } if (escaped && isType(node.parent, 'brace')) { if (node.parent.nodes.length <= 4 && node.parent.count === 1) { node.parent.escaped = true; } else if (node.parent.length <= 3) { node.parent.escaped = true; } } if (!hasQueue(node.parent)) { node.parent.queue = segs; return; } var last = utils.arrayify(queue.pop()); if (node.parent.count > 1 && options.expand) { last = multiply(last, node.parent.count); node.parent.count = 1; } queue.push(utils.join(utils.flatten(last), segs.shift())); queue.push.apply(queue, segs); }) /** * Close */ .set('brace.close', function(node) { var queue = node.parent.queue; var prev = node.parent.parent; var last = prev.queue.pop(); var open = node.parent.open; var close = node.val; if (open && close && isOptimized(node, options)) { open = '('; close = ')'; } // if a close brace exists, and the previous segment is one character // don't wrap the result in braces or parens var ele = utils.last(queue); if (node.parent.count > 1 && options.expand) { ele = multiply(queue.pop(), node.parent.count); node.parent.count = 1; queue.push(ele); } if (close && typeof ele === 'string' && ele.length === 1) { open = ''; close = ''; } if ((isLiteralBrace(node, options) || noInner(node)) && !node.parent.hasEmpty) { queue.push(utils.join(open, queue.pop() || '')); queue = utils.flatten(utils.join(queue, close)); } if (typeof last === 'undefined') { prev.queue = [queue]; } else { prev.queue.push(utils.flatten(utils.join(last, queue))); } }) /** * eos */ .set('eos', function(node) { if (this.input) return; if (options.optimize !== false) { this.output = utils.last(utils.flatten(this.ast.queue)); } else if (Array.isArray(utils.last(this.ast.queue))) { this.output = utils.flatten(this.ast.queue.pop()); } else { this.output = utils.flatten(this.ast.queue); } if (node.parent.count > 1 && options.expand) { this.output = multiply(this.output, node.parent.count); } this.output = utils.arrayify(this.output); this.ast.queue = []; }); }; /** * Multiply the segments in the current brace level */ function multiply(queue, n, options) { return utils.flatten(utils.repeat(utils.arrayify(queue), n)); } /** * Return true if `node` is escaped */ function isEscaped(node) { return node.escaped === true; } /** * Returns true if regex parens should be used for sets. If the parent `type` * is not `brace`, then we're on a root node, which means we should never * expand segments and open/close braces should be `{}` (since this indicates * a brace is missing from the set) */ function isOptimized(node, options) { if (node.parent.isOptimized) return true; return isType(node.parent, 'brace') && !isEscaped(node.parent) && options.expand !== true; } /** * Returns true if the value in `node` should be wrapped in a literal brace. * @return {Boolean} */ function isLiteralBrace(node, options) { return isEscaped(node.parent) || options.optimize !== false; } /** * Returns true if the given `node` does not have an inner value. * @return {Boolean} */ function noInner(node, type) { if (node.parent.queue.length === 1) { return true; } var nodes = node.parent.nodes; return nodes.length === 3 && isType(nodes[0], 'brace.open') && !isType(nodes[1], 'text') && isType(nodes[2], 'brace.close'); } /** * Returns true if the given `node` is the given `type` * @return {Boolean} */ function isType(node, type) { return typeof node !== 'undefined' && node.type === type; } /** * Returns true if the given `node` has a non-empty queue. * @return {Boolean} */ function hasQueue(node) { return Array.isArray(node.queue) && node.queue.length; }