source: trip-planner-front/node_modules/snapdragon/lib/parser.js

Last change on this file was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 10.8 KB
Line 
1'use strict';
2
3var use = require('use');
4var util = require('util');
5var Cache = require('map-cache');
6var define = require('define-property');
7var debug = require('debug')('snapdragon:parser');
8var Position = require('./position');
9var 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
18function 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
29Parser.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
497function 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
509function mapVisit(nodes, fn) {
510 var len = nodes.length;
511 var idx = -1;
512 while (++idx < len) {
513 visit(nodes[idx], fn);
514 }
515}
516
517function hasOpen(node) {
518 return node.nodes && node.nodes[0].type === (node.type + '.open');
519}
520
521function hasClose(node) {
522 return node.nodes && utils.last(node.nodes).type === (node.type + '.close');
523}
524
525function hasDelims(node) {
526 return hasOpen(node) && hasClose(node);
527}
528
529/**
530 * Expose `Parser`
531 */
532
533module.exports = Parser;
Note: See TracBrowser for help on using the repository browser.