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 }