UNPKG

5.03 kBJavaScriptView Raw
1'use strict';
2
3var utils = require('./utils');
4var regexNot = require('regex-not');
5var toRegex = require('to-regex');
6
7/**
8 * Characters to use in negation regex (we want to "not" match
9 * characters that are matched by other parsers)
10 */
11
12var cached;
13var regex = textRegex();
14
15/**
16 * Braces parsers
17 */
18
19module.exports = function(brace) {
20 brace.parser.sets.brace = brace.parser.sets.brace || [];
21 brace.parser
22
23 /**
24 * Brackets: "[...]" (basic, this can be overridden by other parsers)
25 */
26
27 .capture('bracket', function() {
28 var pos = this.position();
29 var m = this.match(/^(?:\[([!^]?)([^\]]{2,}|\]\-)(\]|[^*+?]+)|\[)/);
30 if (!m) return;
31
32 var val = m[0];
33 var negated = m[1] ? '^' : '';
34 var inner = m[2] || '';
35 var close = m[3] || '';
36
37 var esc = this.input.slice(0, 2);
38 if (inner === '' && esc === '\\]') {
39 inner += esc;
40 this.consume(2);
41
42 var str = this.input;
43 var idx = -1;
44 var ch;
45
46 while ((ch = str[++idx])) {
47 this.consume(1);
48 if (ch === ']') {
49 close = ch;
50 break;
51 }
52 inner += ch;
53 }
54 }
55
56 return pos({
57 type: 'bracket',
58 val: val,
59 escaped: close !== ']',
60 negated: negated,
61 inner: inner,
62 close: close
63 });
64 })
65
66 /**
67 * Character parsers
68 */
69
70 .capture('escape', function() {
71 var pos = this.position();
72 var m = this.match(/^(?:\\(.)|\$\{)/);
73 if (!m) return;
74
75 var node = pos({
76 type: 'text',
77 multiplier: 1,
78 val: m[0]
79 });
80
81 if (node.val === '\\\\') {
82 return node;
83 }
84
85 var chars = {'${': '}', '`': '`', '"': '"'};
86 var val = node.val;
87 if (chars[val]) {
88 var str = this.input;
89 var idx = -1;
90 var ch;
91
92 while ((ch = str[++idx])) {
93 this.consume(1);
94 node.val += ch;
95 if (ch === '\\') {
96 ch += str[++idx];
97 node.val += ch;
98 }
99 if (ch === chars[val]) {
100 break;
101 }
102 }
103 }
104
105 if (this.options.unescape !== false) {
106 node.val = node.val.replace(/\\([{}])/g, '$1');
107 }
108
109 return concatNodes.call(this, pos, node);
110 })
111
112 /**
113 * Open
114 */
115
116 .capture('brace.open', function() {
117 var pos = this.position();
118 var m = this.match(/^\{(?!(?:[^\\}]?|,+)\})/);
119 if (!m) return;
120
121 var prev = this.prev();
122 var val = m[0];
123
124 var open = pos({
125 type: 'brace.open',
126 val: val
127 });
128
129 var node = pos({
130 type: 'brace',
131 nodes: [open]
132 });
133
134 utils.define(node, 'parent', prev);
135 utils.define(open, 'parent', node);
136 this.push('brace', node);
137 prev.nodes.push(node);
138 })
139
140 /**
141 * Close
142 */
143
144 .capture('brace.close', function() {
145 var pos = this.position();
146 var m = this.match(/^\}/);
147 if (!m || !m[0]) return;
148
149 var brace = this.pop('brace');
150 var node = pos({
151 type: 'brace.close',
152 val: m[0]
153 });
154
155 if (!this.isType(brace, 'brace')) {
156 if (this.options.strict) {
157 throw new Error('missing opening "{"');
158 }
159 node.type = 'text';
160 node.multiplier = 1;
161 node.escaped = true;
162 return node;
163 }
164
165 brace.nodes.push(node);
166 utils.define(node, 'parent', brace);
167 })
168
169 /**
170 * Inner
171 */
172
173 .capture('text', function() {
174 var pos = this.position();
175 var m = this.match(regex);
176 if (!m) return;
177
178 var node = pos({
179 type: 'text',
180 multiplier: 1,
181 val: m[0]
182 });
183
184 return concatNodes.call(this, pos, node);
185 });
186
187};
188
189/**
190 * Combine text nodes, and calculate empty sets (`{,,}`)
191 * @param {Function} `pos` Function to calculate node position
192 * @param {Object} `node` AST node
193 * @return {Object}
194 */
195
196function concatNodes(pos, node) {
197 var prev = this.prev();
198 var last = utils.last(prev.nodes);
199 var re = /^\{(,+)\}/;
200 var multi;
201
202 var a = node.val.charAt(0);
203 var b = node.val.slice(-1);
204
205 if ((a === '"' && b === '"') || (a === '`' && b === '`') || (a === "'" && b === "'")) {
206 node.val = node.val.slice(1, node.val.length - 1);
207 node.escaped = true;
208 }
209
210 if (re.test(node.val)) {
211 this.input = node.val + this.input;
212 node.val = '';
213 }
214
215 while ((multi = re.exec(this.input))) {
216 this.consume(multi[0].length);
217 node.multiplier *= multi[1].length + 1;
218 }
219
220 if (last.type === 'text' && node.val && node.multiplier === 1 && last.multiplier === 1) {
221 last.val += node.val;
222 return;
223 }
224
225 return node;
226}
227
228/**
229 * Create and cache regex to use for text nodes
230 */
231
232function textRegex(pattern) {
233 if (cached) return cached;
234 var opts = {contains: true, strictClose: false};
235 var not = regexNot.create('(\\$?\\{|\\\\.|\\}|[\\[\\]])', opts);
236 var re = toRegex('^(?:\\{(,+|.?)\\}|' + not + ')', opts);
237 return (cached = re);
238}