Projekt

Obecné

Profil

Stáhnout (7.84 KB) Statistiky
| Větev: | Revize:
1
'use strict';
2

    
3
var Node = require('snapdragon-node');
4
var utils = require('./utils');
5

    
6
/**
7
 * Braces parsers
8
 */
9

    
10
module.exports = function(braces, options) {
11
  braces.parser
12
    .set('bos', function() {
13
      if (!this.parsed) {
14
        this.ast = this.nodes[0] = new Node(this.ast);
15
      }
16
    })
17

    
18
    /**
19
     * Character parsers
20
     */
21

    
22
    .set('escape', function() {
23
      var pos = this.position();
24
      var m = this.match(/^(?:\\(.)|\$\{)/);
25
      if (!m) return;
26

    
27
      var prev = this.prev();
28
      var last = utils.last(prev.nodes);
29

    
30
      var node = pos(new Node({
31
        type: 'text',
32
        multiplier: 1,
33
        val: m[0]
34
      }));
35

    
36
      if (node.val === '\\\\') {
37
        return node;
38
      }
39

    
40
      if (node.val === '${') {
41
        var str = this.input;
42
        var idx = -1;
43
        var ch;
44

    
45
        while ((ch = str[++idx])) {
46
          this.consume(1);
47
          node.val += ch;
48
          if (ch === '\\') {
49
            node.val += str[++idx];
50
            continue;
51
          }
52
          if (ch === '}') {
53
            break;
54
          }
55
        }
56
      }
57

    
58
      if (this.options.unescape !== false) {
59
        node.val = node.val.replace(/\\([{}])/g, '$1');
60
      }
61

    
62
      if (last.val === '"' && this.input.charAt(0) === '"') {
63
        last.val = node.val;
64
        this.consume(1);
65
        return;
66
      }
67

    
68
      return concatNodes.call(this, pos, node, prev, options);
69
    })
70

    
71
    /**
72
     * Brackets: "[...]" (basic, this is overridden by
73
     * other parsers in more advanced implementations)
74
     */
75

    
76
    .set('bracket', function() {
77
      var isInside = this.isInside('brace');
78
      var pos = this.position();
79
      var m = this.match(/^(?:\[([!^]?)([^\]]{2,}|\]-)(\]|[^*+?]+)|\[)/);
80
      if (!m) return;
81

    
82
      var prev = this.prev();
83
      var val = m[0];
84
      var negated = m[1] ? '^' : '';
85
      var inner = m[2] || '';
86
      var close = m[3] || '';
87

    
88
      if (isInside && prev.type === 'brace') {
89
        prev.text = prev.text || '';
90
        prev.text += val;
91
      }
92

    
93
      var esc = this.input.slice(0, 2);
94
      if (inner === '' && esc === '\\]') {
95
        inner += esc;
96
        this.consume(2);
97

    
98
        var str = this.input;
99
        var idx = -1;
100
        var ch;
101

    
102
        while ((ch = str[++idx])) {
103
          this.consume(1);
104
          if (ch === ']') {
105
            close = ch;
106
            break;
107
          }
108
          inner += ch;
109
        }
110
      }
111

    
112
      return pos(new Node({
113
        type: 'bracket',
114
        val: val,
115
        escaped: close !== ']',
116
        negated: negated,
117
        inner: inner,
118
        close: close
119
      }));
120
    })
121

    
122
    /**
123
     * Empty braces (we capture these early to
124
     * speed up processing in the compiler)
125
     */
126

    
127
    .set('multiplier', function() {
128
      var isInside = this.isInside('brace');
129
      var pos = this.position();
130
      var m = this.match(/^\{((?:,|\{,+\})+)\}/);
131
      if (!m) return;
132

    
133
      this.multiplier = true;
134
      var prev = this.prev();
135
      var val = m[0];
136

    
137
      if (isInside && prev.type === 'brace') {
138
        prev.text = prev.text || '';
139
        prev.text += val;
140
      }
141

    
142
      var node = pos(new Node({
143
        type: 'text',
144
        multiplier: 1,
145
        match: m,
146
        val: val
147
      }));
148

    
149
      return concatNodes.call(this, pos, node, prev, options);
150
    })
151

    
152
    /**
153
     * Open
154
     */
155

    
156
    .set('brace.open', function() {
157
      var pos = this.position();
158
      var m = this.match(/^\{(?!(?:[^\\}]?|,+)\})/);
159
      if (!m) return;
160

    
161
      var prev = this.prev();
162
      var last = utils.last(prev.nodes);
163

    
164
      // if the last parsed character was an extglob character
165
      // we need to _not optimize_ the brace pattern because
166
      // it might be mistaken for an extglob by a downstream parser
167
      if (last && last.val && isExtglobChar(last.val.slice(-1))) {
168
        last.optimize = false;
169
      }
170

    
171
      var open = pos(new Node({
172
        type: 'brace.open',
173
        val: m[0]
174
      }));
175

    
176
      var node = pos(new Node({
177
        type: 'brace',
178
        nodes: []
179
      }));
180

    
181
      node.push(open);
182
      prev.push(node);
183
      this.push('brace', node);
184
    })
185

    
186
    /**
187
     * Close
188
     */
189

    
190
    .set('brace.close', function() {
191
      var pos = this.position();
192
      var m = this.match(/^\}/);
193
      if (!m || !m[0]) return;
194

    
195
      var brace = this.pop('brace');
196
      var node = pos(new Node({
197
        type: 'brace.close',
198
        val: m[0]
199
      }));
200

    
201
      if (!this.isType(brace, 'brace')) {
202
        if (this.options.strict) {
203
          throw new Error('missing opening "{"');
204
        }
205
        node.type = 'text';
206
        node.multiplier = 0;
207
        node.escaped = true;
208
        return node;
209
      }
210

    
211
      var prev = this.prev();
212
      var last = utils.last(prev.nodes);
213
      if (last.text) {
214
        var lastNode = utils.last(last.nodes);
215
        if (lastNode.val === ')' && /[!@*?+]\(/.test(last.text)) {
216
          var open = last.nodes[0];
217
          var text = last.nodes[1];
218
          if (open.type === 'brace.open' && text && text.type === 'text') {
219
            text.optimize = false;
220
          }
221
        }
222
      }
223

    
224
      if (brace.nodes.length > 2) {
225
        var first = brace.nodes[1];
226
        if (first.type === 'text' && first.val === ',') {
227
          brace.nodes.splice(1, 1);
228
          brace.nodes.push(first);
229
        }
230
      }
231

    
232
      brace.push(node);
233
    })
234

    
235
    /**
236
     * Capture boundary characters
237
     */
238

    
239
    .set('boundary', function() {
240
      var pos = this.position();
241
      var m = this.match(/^[$^](?!\{)/);
242
      if (!m) return;
243
      return pos(new Node({
244
        type: 'text',
245
        val: m[0]
246
      }));
247
    })
248

    
249
    /**
250
     * One or zero, non-comma characters wrapped in braces
251
     */
252

    
253
    .set('nobrace', function() {
254
      var isInside = this.isInside('brace');
255
      var pos = this.position();
256
      var m = this.match(/^\{[^,]?\}/);
257
      if (!m) return;
258

    
259
      var prev = this.prev();
260
      var val = m[0];
261

    
262
      if (isInside && prev.type === 'brace') {
263
        prev.text = prev.text || '';
264
        prev.text += val;
265
      }
266

    
267
      return pos(new Node({
268
        type: 'text',
269
        multiplier: 0,
270
        val: val
271
      }));
272
    })
273

    
274
    /**
275
     * Text
276
     */
277

    
278
    .set('text', function() {
279
      var isInside = this.isInside('brace');
280
      var pos = this.position();
281
      var m = this.match(/^((?!\\)[^${}[\]])+/);
282
      if (!m) return;
283

    
284
      var prev = this.prev();
285
      var val = m[0];
286

    
287
      if (isInside && prev.type === 'brace') {
288
        prev.text = prev.text || '';
289
        prev.text += val;
290
      }
291

    
292
      var node = pos(new Node({
293
        type: 'text',
294
        multiplier: 1,
295
        val: val
296
      }));
297

    
298
      return concatNodes.call(this, pos, node, prev, options);
299
    });
300
};
301

    
302
/**
303
 * Returns true if the character is an extglob character.
304
 */
305

    
306
function isExtglobChar(ch) {
307
  return ch === '!' || ch === '@' || ch === '*' || ch === '?' || ch === '+';
308
}
309

    
310
/**
311
 * Combine text nodes, and calculate empty sets (`{,,}`)
312
 * @param {Function} `pos` Function to calculate node position
313
 * @param {Object} `node` AST node
314
 * @return {Object}
315
 */
316

    
317
function concatNodes(pos, node, parent, options) {
318
  node.orig = node.val;
319
  var prev = this.prev();
320
  var last = utils.last(prev.nodes);
321
  var isEscaped = false;
322

    
323
  if (node.val.length > 1) {
324
    var a = node.val.charAt(0);
325
    var b = node.val.slice(-1);
326

    
327
    isEscaped = (a === '"' && b === '"')
328
      || (a === "'" && b === "'")
329
      || (a === '`' && b === '`');
330
  }
331

    
332
  if (isEscaped && options.unescape !== false) {
333
    node.val = node.val.slice(1, node.val.length - 1);
334
    node.escaped = true;
335
  }
336

    
337
  if (node.match) {
338
    var match = node.match[1];
339
    if (!match || match.indexOf('}') === -1) {
340
      match = node.match[0];
341
    }
342

    
343
    // replace each set with a single ","
344
    var val = match.replace(/\{/g, ',').replace(/\}/g, '');
345
    node.multiplier *= val.length;
346
    node.val = '';
347
  }
348

    
349
  var simpleText = last.type === 'text'
350
    && last.multiplier === 1
351
    && node.multiplier === 1
352
    && node.val;
353

    
354
  if (simpleText) {
355
    last.val += node.val;
356
    return;
357
  }
358

    
359
  prev.push(node);
360
}
(3-3/4)