30ea5a79f1f02c967e03b85a1cc22e9b9e9c5ae59d5017da49ef96bb2250f53202860c2bd0354c4a4b20eb3e1bea6e94107e3c17639cd948faff6b954ba08a 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. export class Scanner {
  6. constructor() {
  7. this.value = '';
  8. this.pos = 0;
  9. }
  10. static isDigitCharacter(ch) {
  11. return ch >= 48 /* CharCode.Digit0 */ && ch <= 57 /* CharCode.Digit9 */;
  12. }
  13. static isVariableCharacter(ch) {
  14. return ch === 95 /* CharCode.Underline */
  15. || (ch >= 97 /* CharCode.a */ && ch <= 122 /* CharCode.z */)
  16. || (ch >= 65 /* CharCode.A */ && ch <= 90 /* CharCode.Z */);
  17. }
  18. text(value) {
  19. this.value = value;
  20. this.pos = 0;
  21. }
  22. tokenText(token) {
  23. return this.value.substr(token.pos, token.len);
  24. }
  25. next() {
  26. if (this.pos >= this.value.length) {
  27. return { type: 14 /* TokenType.EOF */, pos: this.pos, len: 0 };
  28. }
  29. const pos = this.pos;
  30. let len = 0;
  31. let ch = this.value.charCodeAt(pos);
  32. let type;
  33. // static types
  34. type = Scanner._table[ch];
  35. if (typeof type === 'number') {
  36. this.pos += 1;
  37. return { type, pos, len: 1 };
  38. }
  39. // number
  40. if (Scanner.isDigitCharacter(ch)) {
  41. type = 8 /* TokenType.Int */;
  42. do {
  43. len += 1;
  44. ch = this.value.charCodeAt(pos + len);
  45. } while (Scanner.isDigitCharacter(ch));
  46. this.pos += len;
  47. return { type, pos, len };
  48. }
  49. // variable name
  50. if (Scanner.isVariableCharacter(ch)) {
  51. type = 9 /* TokenType.VariableName */;
  52. do {
  53. ch = this.value.charCodeAt(pos + (++len));
  54. } while (Scanner.isVariableCharacter(ch) || Scanner.isDigitCharacter(ch));
  55. this.pos += len;
  56. return { type, pos, len };
  57. }
  58. // format
  59. type = 10 /* TokenType.Format */;
  60. do {
  61. len += 1;
  62. ch = this.value.charCodeAt(pos + len);
  63. } while (!isNaN(ch)
  64. && typeof Scanner._table[ch] === 'undefined' // not static token
  65. && !Scanner.isDigitCharacter(ch) // not number
  66. && !Scanner.isVariableCharacter(ch) // not variable
  67. );
  68. this.pos += len;
  69. return { type, pos, len };
  70. }
  71. }
  72. Scanner._table = {
  73. [36 /* CharCode.DollarSign */]: 0 /* TokenType.Dollar */,
  74. [58 /* CharCode.Colon */]: 1 /* TokenType.Colon */,
  75. [44 /* CharCode.Comma */]: 2 /* TokenType.Comma */,
  76. [123 /* CharCode.OpenCurlyBrace */]: 3 /* TokenType.CurlyOpen */,
  77. [125 /* CharCode.CloseCurlyBrace */]: 4 /* TokenType.CurlyClose */,
  78. [92 /* CharCode.Backslash */]: 5 /* TokenType.Backslash */,
  79. [47 /* CharCode.Slash */]: 6 /* TokenType.Forwardslash */,
  80. [124 /* CharCode.Pipe */]: 7 /* TokenType.Pipe */,
  81. [43 /* CharCode.Plus */]: 11 /* TokenType.Plus */,
  82. [45 /* CharCode.Dash */]: 12 /* TokenType.Dash */,
  83. [63 /* CharCode.QuestionMark */]: 13 /* TokenType.QuestionMark */,
  84. };
  85. export class Marker {
  86. constructor() {
  87. this._children = [];
  88. }
  89. appendChild(child) {
  90. if (child instanceof Text && this._children[this._children.length - 1] instanceof Text) {
  91. // this and previous child are text -> merge them
  92. this._children[this._children.length - 1].value += child.value;
  93. }
  94. else {
  95. // normal adoption of child
  96. child.parent = this;
  97. this._children.push(child);
  98. }
  99. return this;
  100. }
  101. replace(child, others) {
  102. const { parent } = child;
  103. const idx = parent.children.indexOf(child);
  104. const newChildren = parent.children.slice(0);
  105. newChildren.splice(idx, 1, ...others);
  106. parent._children = newChildren;
  107. (function _fixParent(children, parent) {
  108. for (const child of children) {
  109. child.parent = parent;
  110. _fixParent(child.children, child);
  111. }
  112. })(others, parent);
  113. }
  114. get children() {
  115. return this._children;
  116. }
  117. get snippet() {
  118. let candidate = this;
  119. while (true) {
  120. if (!candidate) {
  121. return undefined;
  122. }
  123. if (candidate instanceof TextmateSnippet) {
  124. return candidate;
  125. }
  126. candidate = candidate.parent;
  127. }
  128. }
  129. toString() {
  130. return this.children.reduce((prev, cur) => prev + cur.toString(), '');
  131. }
  132. len() {
  133. return 0;
  134. }
  135. }
  136. export class Text extends Marker {
  137. constructor(value) {
  138. super();
  139. this.value = value;
  140. }
  141. toString() {
  142. return this.value;
  143. }
  144. len() {
  145. return this.value.length;
  146. }
  147. clone() {
  148. return new Text(this.value);
  149. }
  150. }
  151. export class TransformableMarker extends Marker {
  152. }
  153. export class Placeholder extends TransformableMarker {
  154. constructor(index) {
  155. super();
  156. this.index = index;
  157. }
  158. static compareByIndex(a, b) {
  159. if (a.index === b.index) {
  160. return 0;
  161. }
  162. else if (a.isFinalTabstop) {
  163. return 1;
  164. }
  165. else if (b.isFinalTabstop) {
  166. return -1;
  167. }
  168. else if (a.index < b.index) {
  169. return -1;
  170. }
  171. else if (a.index > b.index) {
  172. return 1;
  173. }
  174. else {
  175. return 0;
  176. }
  177. }
  178. get isFinalTabstop() {
  179. return this.index === 0;
  180. }
  181. get choice() {
  182. return this._children.length === 1 && this._children[0] instanceof Choice
  183. ? this._children[0]
  184. : undefined;
  185. }
  186. clone() {
  187. const ret = new Placeholder(this.index);
  188. if (this.transform) {
  189. ret.transform = this.transform.clone();
  190. }
  191. ret._children = this.children.map(child => child.clone());
  192. return ret;
  193. }
  194. }
  195. export class Choice extends Marker {
  196. constructor() {
  197. super(...arguments);
  198. this.options = [];
  199. }
  200. appendChild(marker) {
  201. if (marker instanceof Text) {
  202. marker.parent = this;
  203. this.options.push(marker);
  204. }
  205. return this;
  206. }
  207. toString() {
  208. return this.options[0].value;
  209. }
  210. len() {
  211. return this.options[0].len();
  212. }
  213. clone() {
  214. const ret = new Choice();
  215. this.options.forEach(ret.appendChild, ret);
  216. return ret;
  217. }
  218. }
  219. export class Transform extends Marker {
  220. constructor() {
  221. super(...arguments);
  222. this.regexp = new RegExp('');
  223. }
  224. resolve(value) {
  225. const _this = this;
  226. let didMatch = false;
  227. let ret = value.replace(this.regexp, function () {
  228. didMatch = true;
  229. return _this._replace(Array.prototype.slice.call(arguments, 0, -2));
  230. });
  231. // when the regex didn't match and when the transform has
  232. // else branches, then run those
  233. if (!didMatch && this._children.some(child => child instanceof FormatString && Boolean(child.elseValue))) {
  234. ret = this._replace([]);
  235. }
  236. return ret;
  237. }
  238. _replace(groups) {
  239. let ret = '';
  240. for (const marker of this._children) {
  241. if (marker instanceof FormatString) {
  242. let value = groups[marker.index] || '';
  243. value = marker.resolve(value);
  244. ret += value;
  245. }
  246. else {
  247. ret += marker.toString();
  248. }
  249. }
  250. return ret;
  251. }
  252. toString() {
  253. return '';
  254. }
  255. clone() {
  256. const ret = new Transform();
  257. ret.regexp = new RegExp(this.regexp.source, '' + (this.regexp.ignoreCase ? 'i' : '') + (this.regexp.global ? 'g' : ''));
  258. ret._children = this.children.map(child => child.clone());
  259. return ret;
  260. }
  261. }
  262. export class FormatString extends Marker {
  263. constructor(index, shorthandName, ifValue, elseValue) {
  264. super();
  265. this.index = index;
  266. this.shorthandName = shorthandName;
  267. this.ifValue = ifValue;
  268. this.elseValue = elseValue;
  269. }
  270. resolve(value) {
  271. if (this.shorthandName === 'upcase') {
  272. return !value ? '' : value.toLocaleUpperCase();
  273. }
  274. else if (this.shorthandName === 'downcase') {
  275. return !value ? '' : value.toLocaleLowerCase();
  276. }
  277. else if (this.shorthandName === 'capitalize') {
  278. return !value ? '' : (value[0].toLocaleUpperCase() + value.substr(1));
  279. }
  280. else if (this.shorthandName === 'pascalcase') {
  281. return !value ? '' : this._toPascalCase(value);
  282. }
  283. else if (this.shorthandName === 'camelcase') {
  284. return !value ? '' : this._toCamelCase(value);
  285. }
  286. else if (Boolean(value) && typeof this.ifValue === 'string') {
  287. return this.ifValue;
  288. }
  289. else if (!Boolean(value) && typeof this.elseValue === 'string') {
  290. return this.elseValue;
  291. }
  292. else {
  293. return value || '';
  294. }
  295. }
  296. _toPascalCase(value) {
  297. const match = value.match(/[a-z0-9]+/gi);
  298. if (!match) {
  299. return value;
  300. }
  301. return match.map(word => {
  302. return word.charAt(0).toUpperCase() + word.substr(1);
  303. })
  304. .join('');
  305. }
  306. _toCamelCase(value) {
  307. const match = value.match(/[a-z0-9]+/gi);
  308. if (!match) {
  309. return value;
  310. }
  311. return match.map((word, index) => {
  312. if (index === 0) {
  313. return word.charAt(0).toLowerCase() + word.substr(1);
  314. }
  315. return word.charAt(0).toUpperCase() + word.substr(1);
  316. })
  317. .join('');
  318. }
  319. clone() {
  320. const ret = new FormatString(this.index, this.shorthandName, this.ifValue, this.elseValue);
  321. return ret;
  322. }
  323. }
  324. export class Variable extends TransformableMarker {
  325. constructor(name) {
  326. super();
  327. this.name = name;
  328. }
  329. resolve(resolver) {
  330. let value = resolver.resolve(this);
  331. if (this.transform) {
  332. value = this.transform.resolve(value || '');
  333. }
  334. if (value !== undefined) {
  335. this._children = [new Text(value)];
  336. return true;
  337. }
  338. return false;
  339. }
  340. clone() {
  341. const ret = new Variable(this.name);
  342. if (this.transform) {
  343. ret.transform = this.transform.clone();
  344. }
  345. ret._children = this.children.map(child => child.clone());
  346. return ret;
  347. }
  348. }
  349. function walk(marker, visitor) {
  350. const stack = [...marker];
  351. while (stack.length > 0) {
  352. const marker = stack.shift();
  353. const recurse = visitor(marker);
  354. if (!recurse) {
  355. break;
  356. }
  357. stack.unshift(...marker.children);
  358. }
  359. }
  360. export class TextmateSnippet extends Marker {
  361. get placeholderInfo() {
  362. if (!this._placeholders) {
  363. // fill in placeholders
  364. const all = [];
  365. let last;
  366. this.walk(function (candidate) {
  367. if (candidate instanceof Placeholder) {
  368. all.push(candidate);
  369. last = !last || last.index < candidate.index ? candidate : last;
  370. }
  371. return true;
  372. });
  373. this._placeholders = { all, last };
  374. }
  375. return this._placeholders;
  376. }
  377. get placeholders() {
  378. const { all } = this.placeholderInfo;
  379. return all;
  380. }
  381. offset(marker) {
  382. let pos = 0;
  383. let found = false;
  384. this.walk(candidate => {
  385. if (candidate === marker) {
  386. found = true;
  387. return false;
  388. }
  389. pos += candidate.len();
  390. return true;
  391. });
  392. if (!found) {
  393. return -1;
  394. }
  395. return pos;
  396. }
  397. fullLen(marker) {
  398. let ret = 0;
  399. walk([marker], marker => {
  400. ret += marker.len();
  401. return true;
  402. });
  403. return ret;
  404. }
  405. enclosingPlaceholders(placeholder) {
  406. const ret = [];
  407. let { parent } = placeholder;
  408. while (parent) {
  409. if (parent instanceof Placeholder) {
  410. ret.push(parent);
  411. }
  412. parent = parent.parent;
  413. }
  414. return ret;
  415. }
  416. resolveVariables(resolver) {
  417. this.walk(candidate => {
  418. if (candidate instanceof Variable) {
  419. if (candidate.resolve(resolver)) {
  420. this._placeholders = undefined;
  421. }
  422. }
  423. return true;
  424. });
  425. return this;
  426. }
  427. appendChild(child) {
  428. this._placeholders = undefined;
  429. return super.appendChild(child);
  430. }
  431. replace(child, others) {
  432. this._placeholders = undefined;
  433. return super.replace(child, others);
  434. }
  435. clone() {
  436. const ret = new TextmateSnippet();
  437. this._children = this.children.map(child => child.clone());
  438. return ret;
  439. }
  440. walk(visitor) {
  441. walk(this.children, visitor);
  442. }
  443. }
  444. export class SnippetParser {
  445. constructor() {
  446. this._scanner = new Scanner();
  447. this._token = { type: 14 /* TokenType.EOF */, pos: 0, len: 0 };
  448. }
  449. static escape(value) {
  450. return value.replace(/\$|}|\\/g, '\\$&');
  451. }
  452. static guessNeedsClipboard(template) {
  453. return /\${?CLIPBOARD/.test(template);
  454. }
  455. parse(value, insertFinalTabstop, enforceFinalTabstop) {
  456. const snippet = new TextmateSnippet();
  457. this.parseFragment(value, snippet);
  458. this.ensureFinalTabstop(snippet, enforceFinalTabstop !== null && enforceFinalTabstop !== void 0 ? enforceFinalTabstop : false, insertFinalTabstop !== null && insertFinalTabstop !== void 0 ? insertFinalTabstop : false);
  459. return snippet;
  460. }
  461. parseFragment(value, snippet) {
  462. const offset = snippet.children.length;
  463. this._scanner.text(value);
  464. this._token = this._scanner.next();
  465. while (this._parse(snippet)) {
  466. // nothing
  467. }
  468. // fill in values for placeholders. the first placeholder of an index
  469. // that has a value defines the value for all placeholders with that index
  470. const placeholderDefaultValues = new Map();
  471. const incompletePlaceholders = [];
  472. snippet.walk(marker => {
  473. if (marker instanceof Placeholder) {
  474. if (marker.isFinalTabstop) {
  475. placeholderDefaultValues.set(0, undefined);
  476. }
  477. else if (!placeholderDefaultValues.has(marker.index) && marker.children.length > 0) {
  478. placeholderDefaultValues.set(marker.index, marker.children);
  479. }
  480. else {
  481. incompletePlaceholders.push(marker);
  482. }
  483. }
  484. return true;
  485. });
  486. for (const placeholder of incompletePlaceholders) {
  487. const defaultValues = placeholderDefaultValues.get(placeholder.index);
  488. if (defaultValues) {
  489. const clone = new Placeholder(placeholder.index);
  490. clone.transform = placeholder.transform;
  491. for (const child of defaultValues) {
  492. clone.appendChild(child.clone());
  493. }
  494. snippet.replace(placeholder, [clone]);
  495. }
  496. }
  497. return snippet.children.slice(offset);
  498. }
  499. ensureFinalTabstop(snippet, enforceFinalTabstop, insertFinalTabstop) {
  500. if (enforceFinalTabstop || insertFinalTabstop && snippet.placeholders.length > 0) {
  501. const finalTabstop = snippet.placeholders.find(p => p.index === 0);
  502. if (!finalTabstop) {
  503. // the snippet uses placeholders but has no
  504. // final tabstop defined -> insert at the end
  505. snippet.appendChild(new Placeholder(0));
  506. }
  507. }
  508. }
  509. _accept(type, value) {
  510. if (type === undefined || this._token.type === type) {
  511. const ret = !value ? true : this._scanner.tokenText(this._token);
  512. this._token = this._scanner.next();
  513. return ret;
  514. }
  515. return false;
  516. }
  517. _backTo(token) {
  518. this._scanner.pos = token.pos + token.len;
  519. this._token = token;
  520. return false;
  521. }
  522. _until(type) {
  523. const start = this._token;
  524. while (this._token.type !== type) {
  525. if (this._token.type === 14 /* TokenType.EOF */) {
  526. return false;
  527. }
  528. else if (this._token.type === 5 /* TokenType.Backslash */) {
  529. const nextToken = this._scanner.next();
  530. if (nextToken.type !== 0 /* TokenType.Dollar */
  531. && nextToken.type !== 4 /* TokenType.CurlyClose */
  532. && nextToken.type !== 5 /* TokenType.Backslash */) {
  533. return false;
  534. }
  535. }
  536. this._token = this._scanner.next();
  537. }
  538. const value = this._scanner.value.substring(start.pos, this._token.pos).replace(/\\(\$|}|\\)/g, '$1');
  539. this._token = this._scanner.next();
  540. return value;
  541. }
  542. _parse(marker) {
  543. return this._parseEscaped(marker)
  544. || this._parseTabstopOrVariableName(marker)
  545. || this._parseComplexPlaceholder(marker)
  546. || this._parseComplexVariable(marker)
  547. || this._parseAnything(marker);
  548. }
  549. // \$, \\, \} -> just text
  550. _parseEscaped(marker) {
  551. let value;
  552. if (value = this._accept(5 /* TokenType.Backslash */, true)) {
  553. // saw a backslash, append escaped token or that backslash
  554. value = this._accept(0 /* TokenType.Dollar */, true)
  555. || this._accept(4 /* TokenType.CurlyClose */, true)
  556. || this._accept(5 /* TokenType.Backslash */, true)
  557. || value;
  558. marker.appendChild(new Text(value));
  559. return true;
  560. }
  561. return false;
  562. }
  563. // $foo -> variable, $1 -> tabstop
  564. _parseTabstopOrVariableName(parent) {
  565. let value;
  566. const token = this._token;
  567. const match = this._accept(0 /* TokenType.Dollar */)
  568. && (value = this._accept(9 /* TokenType.VariableName */, true) || this._accept(8 /* TokenType.Int */, true));
  569. if (!match) {
  570. return this._backTo(token);
  571. }
  572. parent.appendChild(/^\d+$/.test(value)
  573. ? new Placeholder(Number(value))
  574. : new Variable(value));
  575. return true;
  576. }
  577. // ${1:<children>}, ${1} -> placeholder
  578. _parseComplexPlaceholder(parent) {
  579. let index;
  580. const token = this._token;
  581. const match = this._accept(0 /* TokenType.Dollar */)
  582. && this._accept(3 /* TokenType.CurlyOpen */)
  583. && (index = this._accept(8 /* TokenType.Int */, true));
  584. if (!match) {
  585. return this._backTo(token);
  586. }
  587. const placeholder = new Placeholder(Number(index));
  588. if (this._accept(1 /* TokenType.Colon */)) {
  589. // ${1:<children>}
  590. while (true) {
  591. // ...} -> done
  592. if (this._accept(4 /* TokenType.CurlyClose */)) {
  593. parent.appendChild(placeholder);
  594. return true;
  595. }
  596. if (this._parse(placeholder)) {
  597. continue;
  598. }
  599. // fallback
  600. parent.appendChild(new Text('${' + index + ':'));
  601. placeholder.children.forEach(parent.appendChild, parent);
  602. return true;
  603. }
  604. }
  605. else if (placeholder.index > 0 && this._accept(7 /* TokenType.Pipe */)) {
  606. // ${1|one,two,three|}
  607. const choice = new Choice();
  608. while (true) {
  609. if (this._parseChoiceElement(choice)) {
  610. if (this._accept(2 /* TokenType.Comma */)) {
  611. // opt, -> more
  612. continue;
  613. }
  614. if (this._accept(7 /* TokenType.Pipe */)) {
  615. placeholder.appendChild(choice);
  616. if (this._accept(4 /* TokenType.CurlyClose */)) {
  617. // ..|} -> done
  618. parent.appendChild(placeholder);
  619. return true;
  620. }
  621. }
  622. }
  623. this._backTo(token);
  624. return false;
  625. }
  626. }
  627. else if (this._accept(6 /* TokenType.Forwardslash */)) {
  628. // ${1/<regex>/<format>/<options>}
  629. if (this._parseTransform(placeholder)) {
  630. parent.appendChild(placeholder);
  631. return true;
  632. }
  633. this._backTo(token);
  634. return false;
  635. }
  636. else if (this._accept(4 /* TokenType.CurlyClose */)) {
  637. // ${1}
  638. parent.appendChild(placeholder);
  639. return true;
  640. }
  641. else {
  642. // ${1 <- missing curly or colon
  643. return this._backTo(token);
  644. }
  645. }
  646. _parseChoiceElement(parent) {
  647. const token = this._token;
  648. const values = [];
  649. while (true) {
  650. if (this._token.type === 2 /* TokenType.Comma */ || this._token.type === 7 /* TokenType.Pipe */) {
  651. break;
  652. }
  653. let value;
  654. if (value = this._accept(5 /* TokenType.Backslash */, true)) {
  655. // \, \|, or \\
  656. value = this._accept(2 /* TokenType.Comma */, true)
  657. || this._accept(7 /* TokenType.Pipe */, true)
  658. || this._accept(5 /* TokenType.Backslash */, true)
  659. || value;
  660. }
  661. else {
  662. value = this._accept(undefined, true);
  663. }
  664. if (!value) {
  665. // EOF
  666. this._backTo(token);
  667. return false;
  668. }
  669. values.push(value);
  670. }
  671. if (values.length === 0) {
  672. this._backTo(token);
  673. return false;
  674. }
  675. parent.appendChild(new Text(values.join('')));
  676. return true;
  677. }
  678. // ${foo:<children>}, ${foo} -> variable
  679. _parseComplexVariable(parent) {
  680. let name;
  681. const token = this._token;
  682. const match = this._accept(0 /* TokenType.Dollar */)
  683. && this._accept(3 /* TokenType.CurlyOpen */)
  684. && (name = this._accept(9 /* TokenType.VariableName */, true));
  685. if (!match) {
  686. return this._backTo(token);
  687. }
  688. const variable = new Variable(name);
  689. if (this._accept(1 /* TokenType.Colon */)) {
  690. // ${foo:<children>}
  691. while (true) {
  692. // ...} -> done
  693. if (this._accept(4 /* TokenType.CurlyClose */)) {
  694. parent.appendChild(variable);
  695. return true;
  696. }
  697. if (this._parse(variable)) {
  698. continue;
  699. }
  700. // fallback
  701. parent.appendChild(new Text('${' + name + ':'));
  702. variable.children.forEach(parent.appendChild, parent);
  703. return true;
  704. }
  705. }
  706. else if (this._accept(6 /* TokenType.Forwardslash */)) {
  707. // ${foo/<regex>/<format>/<options>}
  708. if (this._parseTransform(variable)) {
  709. parent.appendChild(variable);
  710. return true;
  711. }
  712. this._backTo(token);
  713. return false;
  714. }
  715. else if (this._accept(4 /* TokenType.CurlyClose */)) {
  716. // ${foo}
  717. parent.appendChild(variable);
  718. return true;
  719. }
  720. else {
  721. // ${foo <- missing curly or colon
  722. return this._backTo(token);
  723. }
  724. }
  725. _parseTransform(parent) {
  726. // ...<regex>/<format>/<options>}
  727. const transform = new Transform();
  728. let regexValue = '';
  729. let regexOptions = '';
  730. // (1) /regex
  731. while (true) {
  732. if (this._accept(6 /* TokenType.Forwardslash */)) {
  733. break;
  734. }
  735. let escaped;
  736. if (escaped = this._accept(5 /* TokenType.Backslash */, true)) {
  737. escaped = this._accept(6 /* TokenType.Forwardslash */, true) || escaped;
  738. regexValue += escaped;
  739. continue;
  740. }
  741. if (this._token.type !== 14 /* TokenType.EOF */) {
  742. regexValue += this._accept(undefined, true);
  743. continue;
  744. }
  745. return false;
  746. }
  747. // (2) /format
  748. while (true) {
  749. if (this._accept(6 /* TokenType.Forwardslash */)) {
  750. break;
  751. }
  752. let escaped;
  753. if (escaped = this._accept(5 /* TokenType.Backslash */, true)) {
  754. escaped = this._accept(5 /* TokenType.Backslash */, true) || this._accept(6 /* TokenType.Forwardslash */, true) || escaped;
  755. transform.appendChild(new Text(escaped));
  756. continue;
  757. }
  758. if (this._parseFormatString(transform) || this._parseAnything(transform)) {
  759. continue;
  760. }
  761. return false;
  762. }
  763. // (3) /option
  764. while (true) {
  765. if (this._accept(4 /* TokenType.CurlyClose */)) {
  766. break;
  767. }
  768. if (this._token.type !== 14 /* TokenType.EOF */) {
  769. regexOptions += this._accept(undefined, true);
  770. continue;
  771. }
  772. return false;
  773. }
  774. try {
  775. transform.regexp = new RegExp(regexValue, regexOptions);
  776. }
  777. catch (e) {
  778. // invalid regexp
  779. return false;
  780. }
  781. parent.transform = transform;
  782. return true;
  783. }
  784. _parseFormatString(parent) {
  785. const token = this._token;
  786. if (!this._accept(0 /* TokenType.Dollar */)) {
  787. return false;
  788. }
  789. let complex = false;
  790. if (this._accept(3 /* TokenType.CurlyOpen */)) {
  791. complex = true;
  792. }
  793. const index = this._accept(8 /* TokenType.Int */, true);
  794. if (!index) {
  795. this._backTo(token);
  796. return false;
  797. }
  798. else if (!complex) {
  799. // $1
  800. parent.appendChild(new FormatString(Number(index)));
  801. return true;
  802. }
  803. else if (this._accept(4 /* TokenType.CurlyClose */)) {
  804. // ${1}
  805. parent.appendChild(new FormatString(Number(index)));
  806. return true;
  807. }
  808. else if (!this._accept(1 /* TokenType.Colon */)) {
  809. this._backTo(token);
  810. return false;
  811. }
  812. if (this._accept(6 /* TokenType.Forwardslash */)) {
  813. // ${1:/upcase}
  814. const shorthand = this._accept(9 /* TokenType.VariableName */, true);
  815. if (!shorthand || !this._accept(4 /* TokenType.CurlyClose */)) {
  816. this._backTo(token);
  817. return false;
  818. }
  819. else {
  820. parent.appendChild(new FormatString(Number(index), shorthand));
  821. return true;
  822. }
  823. }
  824. else if (this._accept(11 /* TokenType.Plus */)) {
  825. // ${1:+<if>}
  826. const ifValue = this._until(4 /* TokenType.CurlyClose */);
  827. if (ifValue) {
  828. parent.appendChild(new FormatString(Number(index), undefined, ifValue, undefined));
  829. return true;
  830. }
  831. }
  832. else if (this._accept(12 /* TokenType.Dash */)) {
  833. // ${2:-<else>}
  834. const elseValue = this._until(4 /* TokenType.CurlyClose */);
  835. if (elseValue) {
  836. parent.appendChild(new FormatString(Number(index), undefined, undefined, elseValue));
  837. return true;
  838. }
  839. }
  840. else if (this._accept(13 /* TokenType.QuestionMark */)) {
  841. // ${2:?<if>:<else>}
  842. const ifValue = this._until(1 /* TokenType.Colon */);
  843. if (ifValue) {
  844. const elseValue = this._until(4 /* TokenType.CurlyClose */);
  845. if (elseValue) {
  846. parent.appendChild(new FormatString(Number(index), undefined, ifValue, elseValue));
  847. return true;
  848. }
  849. }
  850. }
  851. else {
  852. // ${1:<else>}
  853. const elseValue = this._until(4 /* TokenType.CurlyClose */);
  854. if (elseValue) {
  855. parent.appendChild(new FormatString(Number(index), undefined, undefined, elseValue));
  856. return true;
  857. }
  858. }
  859. this._backTo(token);
  860. return false;
  861. }
  862. _parseAnything(marker) {
  863. if (this._token.type !== 14 /* TokenType.EOF */) {
  864. marker.appendChild(new Text(this._scanner.tokenText(this._token)));
  865. this._accept(undefined);
  866. return true;
  867. }
  868. return false;
  869. }
  870. }