UNPKG

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