compilers.js
  1  'use strict';
  2  
  3  var utils = require('./utils');
  4  
  5  module.exports = function(braces, options) {
  6    braces.compiler
  7  
  8      /**
  9       * bos
 10       */
 11  
 12      .set('bos', function() {
 13        if (this.output) return;
 14        this.ast.queue = isEscaped(this.ast) ? [this.ast.val] : [];
 15        this.ast.count = 1;
 16      })
 17  
 18      /**
 19       * Square brackets
 20       */
 21  
 22      .set('bracket', function(node) {
 23        var close = node.close;
 24        var open = !node.escaped ? '[' : '\\[';
 25        var negated = node.negated;
 26        var inner = node.inner;
 27  
 28        inner = inner.replace(/\\(?=[\\\w]|$)/g, '\\\\');
 29        if (inner === ']-') {
 30          inner = '\\]\\-';
 31        }
 32  
 33        if (negated && inner.indexOf('.') === -1) {
 34          inner += '.';
 35        }
 36        if (negated && inner.indexOf('/') === -1) {
 37          inner += '/';
 38        }
 39  
 40        var val = open + negated + inner + close;
 41        var queue = node.parent.queue;
 42        var last = utils.arrayify(queue.pop());
 43  
 44        queue.push(utils.join(last, val));
 45        queue.push.apply(queue, []);
 46      })
 47  
 48      /**
 49       * Brace
 50       */
 51  
 52      .set('brace', function(node) {
 53        node.queue = isEscaped(node) ? [node.val] : [];
 54        node.count = 1;
 55        return this.mapVisit(node.nodes);
 56      })
 57  
 58      /**
 59       * Open
 60       */
 61  
 62      .set('brace.open', function(node) {
 63        node.parent.open = node.val;
 64      })
 65  
 66      /**
 67       * Inner
 68       */
 69  
 70      .set('text', function(node) {
 71        var queue = node.parent.queue;
 72        var escaped = node.escaped;
 73        var segs = [node.val];
 74  
 75        if (node.optimize === false) {
 76          options = utils.extend({}, options, {optimize: false});
 77        }
 78  
 79        if (node.multiplier > 1) {
 80          node.parent.count *= node.multiplier;
 81        }
 82  
 83        if (options.quantifiers === true && utils.isQuantifier(node.val)) {
 84          escaped = true;
 85  
 86        } else if (node.val.length > 1) {
 87          if (isType(node.parent, 'brace') && !isEscaped(node)) {
 88            var expanded = utils.expand(node.val, options);
 89            segs = expanded.segs;
 90  
 91            if (expanded.isOptimized) {
 92              node.parent.isOptimized = true;
 93            }
 94  
 95            // if nothing was expanded, we probably have a literal brace
 96            if (!segs.length) {
 97              var val = (expanded.val || node.val);
 98              if (options.unescape !== false) {
 99                // unescape unexpanded brace sequence/set separators
100                val = val.replace(/\\([,.])/g, '$1');
101                // strip quotes
102                val = val.replace(/["'`]/g, '');
103              }
104  
105              segs = [val];
106              escaped = true;
107            }
108          }
109  
110        } else if (node.val === ',') {
111          if (options.expand) {
112            node.parent.queue.push(['']);
113            segs = [''];
114          } else {
115            segs = ['|'];
116          }
117        } else {
118          escaped = true;
119        }
120  
121        if (escaped && isType(node.parent, 'brace')) {
122          if (node.parent.nodes.length <= 4 && node.parent.count === 1) {
123            node.parent.escaped = true;
124          } else if (node.parent.length <= 3) {
125            node.parent.escaped = true;
126          }
127        }
128  
129        if (!hasQueue(node.parent)) {
130          node.parent.queue = segs;
131          return;
132        }
133  
134        var last = utils.arrayify(queue.pop());
135        if (node.parent.count > 1 && options.expand) {
136          last = multiply(last, node.parent.count);
137          node.parent.count = 1;
138        }
139  
140        queue.push(utils.join(utils.flatten(last), segs.shift()));
141        queue.push.apply(queue, segs);
142      })
143  
144      /**
145       * Close
146       */
147  
148      .set('brace.close', function(node) {
149        var queue = node.parent.queue;
150        var prev = node.parent.parent;
151        var last = prev.queue.pop();
152        var open = node.parent.open;
153        var close = node.val;
154  
155        if (open && close && isOptimized(node, options)) {
156          open = '(';
157          close = ')';
158        }
159  
160        // if a close brace exists, and the previous segment is one character
161        // don't wrap the result in braces or parens
162        var ele = utils.last(queue);
163        if (node.parent.count > 1 && options.expand) {
164          ele = multiply(queue.pop(), node.parent.count);
165          node.parent.count = 1;
166          queue.push(ele);
167        }
168  
169        if (close && typeof ele === 'string' && ele.length === 1) {
170          open = '';
171          close = '';
172        }
173  
174        if ((isLiteralBrace(node, options) || noInner(node)) && !node.parent.hasEmpty) {
175          queue.push(utils.join(open, queue.pop() || ''));
176          queue = utils.flatten(utils.join(queue, close));
177        }
178  
179        if (typeof last === 'undefined') {
180          prev.queue = [queue];
181        } else {
182          prev.queue.push(utils.flatten(utils.join(last, queue)));
183        }
184      })
185  
186      /**
187       * eos
188       */
189  
190      .set('eos', function(node) {
191        if (this.input) return;
192  
193        if (options.optimize !== false) {
194          this.output = utils.last(utils.flatten(this.ast.queue));
195        } else if (Array.isArray(utils.last(this.ast.queue))) {
196          this.output = utils.flatten(this.ast.queue.pop());
197        } else {
198          this.output = utils.flatten(this.ast.queue);
199        }
200  
201        if (node.parent.count > 1 && options.expand) {
202          this.output = multiply(this.output, node.parent.count);
203        }
204  
205        this.output = utils.arrayify(this.output);
206        this.ast.queue = [];
207      });
208  
209  };
210  
211  /**
212   * Multiply the segments in the current brace level
213   */
214  
215  function multiply(queue, n, options) {
216    return utils.flatten(utils.repeat(utils.arrayify(queue), n));
217  }
218  
219  /**
220   * Return true if `node` is escaped
221   */
222  
223  function isEscaped(node) {
224    return node.escaped === true;
225  }
226  
227  /**
228   * Returns true if regex parens should be used for sets. If the parent `type`
229   * is not `brace`, then we're on a root node, which means we should never
230   * expand segments and open/close braces should be `{}` (since this indicates
231   * a brace is missing from the set)
232   */
233  
234  function isOptimized(node, options) {
235    if (node.parent.isOptimized) return true;
236    return isType(node.parent, 'brace')
237      && !isEscaped(node.parent)
238      && options.expand !== true;
239  }
240  
241  /**
242   * Returns true if the value in `node` should be wrapped in a literal brace.
243   * @return {Boolean}
244   */
245  
246  function isLiteralBrace(node, options) {
247    return isEscaped(node.parent) || options.optimize !== false;
248  }
249  
250  /**
251   * Returns true if the given `node` does not have an inner value.
252   * @return {Boolean}
253   */
254  
255  function noInner(node, type) {
256    if (node.parent.queue.length === 1) {
257      return true;
258    }
259    var nodes = node.parent.nodes;
260    return nodes.length === 3
261      && isType(nodes[0], 'brace.open')
262      && !isType(nodes[1], 'text')
263      && isType(nodes[2], 'brace.close');
264  }
265  
266  /**
267   * Returns true if the given `node` is the given `type`
268   * @return {Boolean}
269   */
270  
271  function isType(node, type) {
272    return typeof node !== 'undefined' && node.type === type;
273  }
274  
275  /**
276   * Returns true if the given `node` has a non-empty queue.
277   * @return {Boolean}
278   */
279  
280  function hasQueue(node) {
281    return Array.isArray(node.queue) && node.queue.length;
282  }