parsers-parens.js 11 KB

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