.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / snapdragon / lib / parser.js
1 'use strict';
2
3 var use = require('use');
4 var util = require('util');
5 var Cache = require('map-cache');
6 var define = require('define-property');
7 var debug = require('debug')('snapdragon:parser');
8 var Position = require('./position');
9 var utils = require('./utils');
10
11 /**
12  * Create a new `Parser` with the given `input` and `options`.
13  * @param {String} `input`
14  * @param {Object} `options`
15  * @api public
16  */
17
18 function Parser(options) {
19   debug('initializing', __filename);
20   this.options = utils.extend({source: 'string'}, options);
21   this.init(this.options);
22   use(this);
23 }
24
25 /**
26  * Prototype methods
27  */
28
29 Parser.prototype = {
30   constructor: Parser,
31
32   init: function(options) {
33     this.orig = '';
34     this.input = '';
35     this.parsed = '';
36
37     this.column = 1;
38     this.line = 1;
39
40     this.regex = new Cache();
41     this.errors = this.errors || [];
42     this.parsers = this.parsers || {};
43     this.types = this.types || [];
44     this.sets = this.sets || {};
45     this.fns = this.fns || [];
46     this.currentType = 'root';
47
48     var pos = this.position();
49     this.bos = pos({type: 'bos', val: ''});
50
51     this.ast = {
52       type: 'root',
53       errors: this.errors,
54       nodes: [this.bos]
55     };
56
57     define(this.bos, 'parent', this.ast);
58     this.nodes = [this.ast];
59
60     this.count = 0;
61     this.setCount = 0;
62     this.stack = [];
63   },
64
65   /**
66    * Throw a formatted error with the cursor column and `msg`.
67    * @param {String} `msg` Message to use in the Error.
68    */
69
70   error: function(msg, node) {
71     var pos = node.position || {start: {column: 0, line: 0}};
72     var line = pos.start.line;
73     var column = pos.start.column;
74     var source = this.options.source;
75
76     var message = source + ' <line:' + line + ' column:' + column + '>: ' + msg;
77     var err = new Error(message);
78     err.source = source;
79     err.reason = msg;
80     err.pos = pos;
81
82     if (this.options.silent) {
83       this.errors.push(err);
84     } else {
85       throw err;
86     }
87   },
88
89   /**
90    * Define a non-enumberable property on the `Parser` instance.
91    *
92    * ```js
93    * parser.define('foo', 'bar');
94    * ```
95    * @name .define
96    * @param {String} `key` propery name
97    * @param {any} `val` property value
98    * @return {Object} Returns the Parser instance for chaining.
99    * @api public
100    */
101
102   define: function(key, val) {
103     define(this, key, val);
104     return this;
105   },
106
107   /**
108    * Mark position and patch `node.position`.
109    */
110
111   position: function() {
112     var start = { line: this.line, column: this.column };
113     var self = this;
114
115     return function(node) {
116       define(node, 'position', new Position(start, self));
117       return node;
118     };
119   },
120
121   /**
122    * Set parser `name` with the given `fn`
123    * @param {String} `name`
124    * @param {Function} `fn`
125    * @api public
126    */
127
128   set: function(type, fn) {
129     if (this.types.indexOf(type) === -1) {
130       this.types.push(type);
131     }
132     this.parsers[type] = fn.bind(this);
133     return this;
134   },
135
136   /**
137    * Get parser `name`
138    * @param {String} `name`
139    * @api public
140    */
141
142   get: function(name) {
143     return this.parsers[name];
144   },
145
146   /**
147    * Push a `token` onto the `type` stack.
148    *
149    * @param {String} `type`
150    * @return {Object} `token`
151    * @api public
152    */
153
154   push: function(type, token) {
155     this.sets[type] = this.sets[type] || [];
156     this.count++;
157     this.stack.push(token);
158     return this.sets[type].push(token);
159   },
160
161   /**
162    * Pop a token off of the `type` stack
163    * @param {String} `type`
164    * @returns {Object} Returns a token
165    * @api public
166    */
167
168   pop: function(type) {
169     this.sets[type] = this.sets[type] || [];
170     this.count--;
171     this.stack.pop();
172     return this.sets[type].pop();
173   },
174
175   /**
176    * Return true if inside a `stack` node. Types are `braces`, `parens` or `brackets`.
177    *
178    * @param {String} `type`
179    * @return {Boolean}
180    * @api public
181    */
182
183   isInside: function(type) {
184     this.sets[type] = this.sets[type] || [];
185     return this.sets[type].length > 0;
186   },
187
188   /**
189    * Return true if `node` is the given `type`.
190    *
191    * ```js
192    * parser.isType(node, 'brace');
193    * ```
194    * @param {Object} `node`
195    * @param {String} `type`
196    * @return {Boolean}
197    * @api public
198    */
199
200   isType: function(node, type) {
201     return node && node.type === type;
202   },
203
204   /**
205    * Get the previous AST node
206    * @return {Object}
207    */
208
209   prev: function(n) {
210     return this.stack.length > 0
211       ? utils.last(this.stack, n)
212       : utils.last(this.nodes, n);
213   },
214
215   /**
216    * Update line and column based on `str`.
217    */
218
219   consume: function(len) {
220     this.input = this.input.substr(len);
221   },
222
223   /**
224    * Update column based on `str`.
225    */
226
227   updatePosition: function(str, len) {
228     var lines = str.match(/\n/g);
229     if (lines) this.line += lines.length;
230     var i = str.lastIndexOf('\n');
231     this.column = ~i ? len - i : this.column + len;
232     this.parsed += str;
233     this.consume(len);
234   },
235
236   /**
237    * Match `regex`, return captures, and update the cursor position by `match[0]` length.
238    * @param {RegExp} `regex`
239    * @return {Object}
240    */
241
242   match: function(regex) {
243     var m = regex.exec(this.input);
244     if (m) {
245       this.updatePosition(m[0], m[0].length);
246       return m;
247     }
248   },
249
250   /**
251    * Capture `type` with the given regex.
252    * @param {String} `type`
253    * @param {RegExp} `regex`
254    * @return {Function}
255    */
256
257   capture: function(type, regex) {
258     if (typeof regex === 'function') {
259       return this.set.apply(this, arguments);
260     }
261
262     this.regex.set(type, regex);
263     this.set(type, function() {
264       var parsed = this.parsed;
265       var pos = this.position();
266       var m = this.match(regex);
267       if (!m || !m[0]) return;
268
269       var prev = this.prev();
270       var node = pos({
271         type: type,
272         val: m[0],
273         parsed: parsed,
274         rest: this.input
275       });
276
277       if (m[1]) {
278         node.inner = m[1];
279       }
280
281       define(node, 'inside', this.stack.length > 0);
282       define(node, 'parent', prev);
283       prev.nodes.push(node);
284     }.bind(this));
285     return this;
286   },
287
288   /**
289    * Create a parser with open and close for parens,
290    * brackets or braces
291    */
292
293   capturePair: function(type, openRegex, closeRegex, fn) {
294     this.sets[type] = this.sets[type] || [];
295
296     /**
297      * Open
298      */
299
300     this.set(type + '.open', function() {
301       var parsed = this.parsed;
302       var pos = this.position();
303       var m = this.match(openRegex);
304       if (!m || !m[0]) return;
305
306       var val = m[0];
307       this.setCount++;
308       this.specialChars = true;
309       var open = pos({
310         type: type + '.open',
311         val: val,
312         rest: this.input
313       });
314
315       if (typeof m[1] !== 'undefined') {
316         open.inner = m[1];
317       }
318
319       var prev = this.prev();
320       var node = pos({
321         type: type,
322         nodes: [open]
323       });
324
325       define(node, 'rest', this.input);
326       define(node, 'parsed', parsed);
327       define(node, 'prefix', m[1]);
328       define(node, 'parent', prev);
329       define(open, 'parent', node);
330
331       if (typeof fn === 'function') {
332         fn.call(this, open, node);
333       }
334
335       this.push(type, node);
336       prev.nodes.push(node);
337     });
338
339     /**
340      * Close
341      */
342
343     this.set(type + '.close', function() {
344       var pos = this.position();
345       var m = this.match(closeRegex);
346       if (!m || !m[0]) return;
347
348       var parent = this.pop(type);
349       var node = pos({
350         type: type + '.close',
351         rest: this.input,
352         suffix: m[1],
353         val: m[0]
354       });
355
356       if (!this.isType(parent, type)) {
357         if (this.options.strict) {
358           throw new Error('missing opening "' + type + '"');
359         }
360
361         this.setCount--;
362         node.escaped = true;
363         return node;
364       }
365
366       if (node.suffix === '\\') {
367         parent.escaped = true;
368         node.escaped = true;
369       }
370
371       parent.nodes.push(node);
372       define(node, 'parent', parent);
373     });
374
375     return this;
376   },
377
378   /**
379    * Capture end-of-string
380    */
381
382   eos: function() {
383     var pos = this.position();
384     if (this.input) return;
385     var prev = this.prev();
386
387     while (prev.type !== 'root' && !prev.visited) {
388       if (this.options.strict === true) {
389         throw new SyntaxError('invalid syntax:' + util.inspect(prev, null, 2));
390       }
391
392       if (!hasDelims(prev)) {
393         prev.parent.escaped = true;
394         prev.escaped = true;
395       }
396
397       visit(prev, function(node) {
398         if (!hasDelims(node.parent)) {
399           node.parent.escaped = true;
400           node.escaped = true;
401         }
402       });
403
404       prev = prev.parent;
405     }
406
407     var tok = pos({
408       type: 'eos',
409       val: this.append || ''
410     });
411
412     define(tok, 'parent', this.ast);
413     return tok;
414   },
415
416   /**
417    * Run parsers to advance the cursor position
418    */
419
420   next: function() {
421     var parsed = this.parsed;
422     var len = this.types.length;
423     var idx = -1;
424     var tok;
425
426     while (++idx < len) {
427       if ((tok = this.parsers[this.types[idx]].call(this))) {
428         define(tok, 'rest', this.input);
429         define(tok, 'parsed', parsed);
430         this.last = tok;
431         return tok;
432       }
433     }
434   },
435
436   /**
437    * Parse the given string.
438    * @return {Array}
439    */
440
441   parse: function(input) {
442     if (typeof input !== 'string') {
443       throw new TypeError('expected a string');
444     }
445
446     this.init(this.options);
447     this.orig = input;
448     this.input = input;
449     var self = this;
450
451     function parse() {
452       // check input before calling `.next()`
453       input = self.input;
454
455       // get the next AST ndoe
456       var node = self.next();
457       if (node) {
458         var prev = self.prev();
459         if (prev) {
460           define(node, 'parent', prev);
461           if (prev.nodes) {
462             prev.nodes.push(node);
463           }
464         }
465
466         if (self.sets.hasOwnProperty(prev.type)) {
467           self.currentType = prev.type;
468         }
469       }
470
471       // if we got here but input is not changed, throw an error
472       if (self.input && input === self.input) {
473         throw new Error('no parsers registered for: "' + self.input.slice(0, 5) + '"');
474       }
475     }
476
477     while (this.input) parse();
478     if (this.stack.length && this.options.strict) {
479       var node = this.stack.pop();
480       throw this.error('missing opening ' + node.type + ': "' + this.orig + '"');
481     }
482
483     var eos = this.eos();
484     var tok = this.prev();
485     if (tok.type !== 'eos') {
486       this.ast.nodes.push(eos);
487     }
488
489     return this.ast;
490   }
491 };
492
493 /**
494  * Visit `node` with the given `fn`
495  */
496
497 function visit(node, fn) {
498   if (!node.visited) {
499     define(node, 'visited', true);
500     return node.nodes ? mapVisit(node.nodes, fn) : fn(node);
501   }
502   return node;
503 }
504
505 /**
506  * Map visit over array of `nodes`.
507  */
508
509 function mapVisit(nodes, fn) {
510   var len = nodes.length;
511   var idx = -1;
512   while (++idx < len) {
513     visit(nodes[idx], fn);
514   }
515 }
516
517 function hasOpen(node) {
518   return node.nodes && node.nodes[0].type === (node.type + '.open');
519 }
520
521 function hasClose(node) {
522   return node.nodes && utils.last(node.nodes).type === (node.type + '.close');
523 }
524
525 function hasDelims(node) {
526   return hasOpen(node) && hasClose(node);
527 }
528
529 /**
530  * Expose `Parser`
531  */
532
533 module.exports = Parser;