parsers-new.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. 'use strict';
  2. var Node = require('snapdragon-node');
  3. var utils = require('./utils');
  4. /**
  5. * Braces parsers
  6. */
  7. module.exports = function(braces, options) {
  8. function update(parent, val) {
  9. if (parent && parent.type === 'brace') {
  10. parent.text = parent.text || '';
  11. parent.text += val;
  12. }
  13. }
  14. braces.parser
  15. .set('bos', function() {
  16. if (!this.parsed) {
  17. this.ast = this.nodes[0] = new Node(this.ast);
  18. }
  19. })
  20. /**
  21. * Character parsers
  22. */
  23. .set('escape', function() {
  24. var pos = this.position();
  25. var m = this.match(/^(?:\\(.)|\$\{)/);
  26. if (!m) return;
  27. var prev = this.prev();
  28. var last = utils.last(prev.nodes);
  29. update(prev, m[0]);
  30. var node = pos(new Node({
  31. type: 'text',
  32. multiplier: 1,
  33. val: m[0]
  34. }));
  35. if (node.val === '\\\\') {
  36. return node;
  37. }
  38. if (node.val === '${') {
  39. var str = this.input;
  40. var idx = -1;
  41. var ch;
  42. while ((ch = str[++idx])) {
  43. this.consume(1);
  44. node.val += ch;
  45. if (ch === '\\') {
  46. node.val += str[++idx];
  47. continue;
  48. }
  49. if (ch === '}') {
  50. break;
  51. }
  52. }
  53. }
  54. if (last.val === '"' && this.input.charAt(0) === '"') {
  55. last.val = node.val;
  56. this.consume(1);
  57. return;
  58. }
  59. return concatNodes.call(this, pos, node, prev, options);
  60. })
  61. /**
  62. * Brackets: "[...]" (basic, this is overridden by
  63. * other parsers in more advanced implementations)
  64. */
  65. .set('bracket', function() {
  66. var pos = this.position();
  67. var m = this.match(/^(?!\\)(?:\[([!^]?)([^\]]{2,}|\]\-)(\]|[^*+?]+)|\[)/);
  68. if (!m) return;
  69. var prev = this.prev();
  70. var val = m[0];
  71. var negated = m[1] ? '^' : '';
  72. var inner = m[2] || '';
  73. var close = m[3] || '';
  74. update(prev, m[0]);
  75. if (this.input[0] === ']') {
  76. this.consume(1);
  77. inner = '\\]';
  78. val = '\\[';
  79. }
  80. if (this.input.slice(0, 2) === '\\]') {
  81. this.consume(2);
  82. inner = '\\]';
  83. val = '\\[';
  84. }
  85. var node = pos(new Node({
  86. type: 'bracket',
  87. val: val,
  88. escaped: close !== ']',
  89. negated: negated,
  90. inner: inner,
  91. close: close
  92. }));
  93. return node;
  94. })
  95. /**
  96. * Empty braces (we capture these early to
  97. * speed up processing in the compiler)
  98. */
  99. .set('multiplier', function() {
  100. var pos = this.position();
  101. var m = this.match(/^\{(,+(?:(\{,+\})*),*|,*(?:(\{,+\})*),+)\}/);
  102. if (!m) return;
  103. this.multiplier = true;
  104. var prev = this.prev();
  105. var val = m[0];
  106. var node = pos(new Node({
  107. type: 'text',
  108. multiplier: 1,
  109. match: m,
  110. val: val
  111. }));
  112. update(prev, m[0]);
  113. return concatNodes.call(this, pos, node, prev, options);
  114. })
  115. /**
  116. * Paren open: "("
  117. */
  118. .set('paren.open', function() {
  119. var pos = this.position();
  120. var m = this.match(/^\(/);
  121. if (!m) return;
  122. var prev = this.prev();
  123. update(prev, m[0]);
  124. var node = pos(new Node({
  125. type: 'paren',
  126. nodes: []
  127. }));
  128. var open = pos(new Node({
  129. type: 'paren.open',
  130. val: m[0]
  131. }));
  132. node.push(open);
  133. prev.push(node);
  134. this.push('paren', node);
  135. })
  136. /**
  137. * Paren close: ")"
  138. */
  139. .set('paren.close', function() {
  140. var pos = this.position();
  141. var m = this.match(/^\)/);
  142. if (!m) return;
  143. var paren = this.pop('paren');
  144. var node = pos(new Node({
  145. type: 'paren.close',
  146. val: m[0]
  147. }));
  148. if (!this.isType(paren, 'paren')) {
  149. if (this.options.strict) {
  150. throw new Error('missing opening "("');
  151. }
  152. node.type = 'text';
  153. node.escaped = true;
  154. return node;
  155. }
  156. update(this.prev(), m[0]);
  157. paren.push(node);
  158. paren.type = 'text';
  159. paren.val = '';
  160. // visit(paren, function(n) {
  161. // if (n.type === 'bracket') {
  162. // paren.val += n.val;
  163. // if (n.inner === '\\]') {
  164. // paren.val += n.inner;
  165. // }
  166. // paren.val += n.close;
  167. // } else if (n.type === 'paren.open' || n.type === 'paren.close') {
  168. // paren.val += n.val;
  169. // paren.unescape = false;
  170. // } else if (n.val) {
  171. // paren.val += n.val;
  172. // }
  173. // });
  174. delete paren.nodes;
  175. })
  176. /**
  177. * Brace open: "{"
  178. */
  179. .set('brace.open', function() {
  180. var pos = this.position();
  181. var m = this.match(/^\{(?!(?:[^\\}]?|,+)\})/);
  182. if (!m) return;
  183. var prev = this.prev();
  184. var last = utils.last(prev.nodes);
  185. var open = pos(new Node({
  186. type: 'brace.open',
  187. val: m[0]
  188. }));
  189. update(prev, m[0]);
  190. var node = pos(new Node({
  191. type: 'brace',
  192. nodes: []
  193. }));
  194. var lastChar = (last && last.val) ? last.val.slice(-1) : '';
  195. node.isExtglob = utils.isExtglobChar(lastChar);
  196. // if the last parsed character was an extglob character
  197. // we need to _not optimize_ the brace pattern because
  198. // it might be mistaken for an extglob by a downstream parser
  199. if (lastChar && node.isExtglob && !this.options.toExtglob) {
  200. last.optimize = false;
  201. }
  202. if (this.options.toExtglob) {
  203. node.lastChar = node.isExtglob ? lastChar : '';
  204. node.toExtglob = true;
  205. }
  206. node.push(open);
  207. prev.push(node);
  208. this.push('brace', node);
  209. })
  210. /**
  211. * Brace close: "}"
  212. */
  213. .set('brace.close', function() {
  214. var pos = this.position();
  215. var m = this.match(/^\}/);
  216. if (!m || !m[0]) return;
  217. var brace = this.pop('brace');
  218. var node = pos(new Node({
  219. type: 'brace.close',
  220. val: m[0]
  221. }));
  222. if (!this.isType(brace, 'brace')) {
  223. if (this.options.strict) {
  224. throw new Error('missing opening "{"');
  225. }
  226. node.type = 'text';
  227. node.multiplier = 0;
  228. node.escaped = true;
  229. return node;
  230. }
  231. var prev = this.prev();
  232. update(prev, node.val);
  233. var last = utils.last(prev.nodes);
  234. if (last.type === 'brace') {
  235. var lastNode = utils.last(last.nodes);
  236. var lastVal = lastNode.val ? lastNode.val[0] : null;
  237. if (lastVal === ')') {
  238. if (/[!@*?+]\(/.test(last.text)) {
  239. var open = last.nodes[0];
  240. var text = last.nodes[1];
  241. if (open.type === 'brace.open' && text && text.type === 'text') {
  242. text.optimize = false;
  243. }
  244. }
  245. }
  246. }
  247. if (brace.nodes.length > 2) {
  248. var first = brace.nodes[1];
  249. if (first.type === 'text' && first.val === ',') {
  250. brace.nodes.splice(1, 1);
  251. brace.nodes.push(first);
  252. }
  253. }
  254. brace.push(node);
  255. })
  256. /**
  257. * Capture boundary characters
  258. */
  259. .set('boundary', function() {
  260. var pos = this.position();
  261. var m = this.match(/^[$^](?!\{)/);
  262. if (!m) return;
  263. update(this.prev(), m[0]);
  264. return pos(new Node({
  265. type: 'text',
  266. val: m[0]
  267. }));
  268. })
  269. /**
  270. * One or zero, non-comma characters wrapped in braces
  271. */
  272. .set('nobrace', function() {
  273. var pos = this.position();
  274. var m = this.match(/^\{[^,]?\}/);
  275. if (!m) return;
  276. var prev = this.prev();
  277. var val = m[0];
  278. update(prev, val);
  279. return pos(new Node({
  280. type: 'text',
  281. multiplier: 0,
  282. val: val
  283. }));
  284. })
  285. /**
  286. * Text
  287. */
  288. .set('text', function() {
  289. var parsed = this.parsed;
  290. var pos = this.position();
  291. var m = this.match(/^((?!\\)[^${}()\[\]])+/);
  292. if (!m) return;
  293. var prev = this.prev();
  294. var val = m[0];
  295. if (utils.isExtglobChar(m[1]) && this.input[0] === '{') {
  296. val = val.slice(0, val.length - 1) + '\\' + val.slice(-1);
  297. }
  298. var node = pos(new Node({
  299. type: 'text',
  300. multiplier: 1,
  301. val: val
  302. }));
  303. update(prev, val);
  304. return concatNodes.call(this, pos, node, prev, options);
  305. });
  306. };
  307. /**
  308. * Combine text nodes, and calculate empty sets (`{,,}`)
  309. * @param {Function} `pos` Function to calculate node position
  310. * @param {Object} `node` AST node
  311. * @return {Object}
  312. */
  313. function concatNodes(pos, node, parent, options) {
  314. node.orig = node.val;
  315. var prev = this.prev();
  316. var last = utils.last(prev.nodes);
  317. var isEscaped = false;
  318. if (node.val.length > 1) {
  319. var a = node.val.charAt(0);
  320. var b = node.val.slice(-1);
  321. isEscaped = (a === '"' && b === '"')
  322. || (a === "'" && b === "'")
  323. || (a === '`' && b === '`');
  324. }
  325. if (isEscaped && options.unescape !== false) {
  326. node.val = node.val.slice(1, node.val.length - 1);
  327. node.escaped = true;
  328. }
  329. if (node.match) {
  330. var match = node.match[1];
  331. if (!match || match.indexOf('}') === -1) {
  332. match = node.match[0];
  333. }
  334. // replace each set with a single ","
  335. var val = match.replace(/\{/g, ',').replace(/\}/g, '');
  336. node.multiplier *= val.length;
  337. node.val = '';
  338. }
  339. var simpleText = last.type === 'text'
  340. && last.multiplier === 1
  341. && node.multiplier === 1
  342. && node.val;
  343. if (simpleText) {
  344. last.val += node.val;
  345. return;
  346. }
  347. prev.push(node);
  348. }
  349. function visit(node, fn) {
  350. fn(node);
  351. return node.nodes ? mapVisit(node, fn) : node;
  352. }
  353. function mapVisit(node, fn) {
  354. for (let i = 0; i < node.nodes.length; i++) {
  355. visit(node.nodes[i], fn);
  356. }
  357. return node;
  358. }