snippetParser.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  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 /* Digit0 */ && ch <= 57 /* Digit9 */;
  12. }
  13. static isVariableCharacter(ch) {
  14. return ch === 95 /* Underline */
  15. || (ch >= 97 /* a */ && ch <= 122 /* z */)
  16. || (ch >= 65 /* A */ && ch <= 90 /* 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 /* EOF */, pos: this.pos, len: 0 };
  28. }
  29. let 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 /* 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 /* 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 /* 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 /* DollarSign */]: 0 /* Dollar */,
  74. [58 /* Colon */]: 1 /* Colon */,
  75. [44 /* Comma */]: 2 /* Comma */,
  76. [123 /* OpenCurlyBrace */]: 3 /* CurlyOpen */,
  77. [125 /* CloseCurlyBrace */]: 4 /* CurlyClose */,
  78. [92 /* Backslash */]: 5 /* Backslash */,
  79. [47 /* Slash */]: 6 /* Forwardslash */,
  80. [124 /* Pipe */]: 7 /* Pipe */,
  81. [43 /* Plus */]: 11 /* Plus */,
  82. [45 /* Dash */]: 12 /* Dash */,
  83. [63 /* QuestionMark */]: 13 /* 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. let 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. let 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. let 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()
  303. + word.substr(1).toLowerCase();
  304. })
  305. .join('');
  306. }
  307. _toCamelCase(value) {
  308. const match = value.match(/[a-z0-9]+/gi);
  309. if (!match) {
  310. return value;
  311. }
  312. return match.map((word, index) => {
  313. if (index === 0) {
  314. return word.toLowerCase();
  315. }
  316. else {
  317. return word.charAt(0).toUpperCase()
  318. + word.substr(1).toLowerCase();
  319. }
  320. })
  321. .join('');
  322. }
  323. clone() {
  324. let ret = new FormatString(this.index, this.shorthandName, this.ifValue, this.elseValue);
  325. return ret;
  326. }
  327. }
  328. export class Variable extends TransformableMarker {
  329. constructor(name) {
  330. super();
  331. this.name = name;
  332. }
  333. resolve(resolver) {
  334. let value = resolver.resolve(this);
  335. if (this.transform) {
  336. value = this.transform.resolve(value || '');
  337. }
  338. if (value !== undefined) {
  339. this._children = [new Text(value)];
  340. return true;
  341. }
  342. return false;
  343. }
  344. clone() {
  345. const ret = new Variable(this.name);
  346. if (this.transform) {
  347. ret.transform = this.transform.clone();
  348. }
  349. ret._children = this.children.map(child => child.clone());
  350. return ret;
  351. }
  352. }
  353. function walk(marker, visitor) {
  354. const stack = [...marker];
  355. while (stack.length > 0) {
  356. const marker = stack.shift();
  357. const recurse = visitor(marker);
  358. if (!recurse) {
  359. break;
  360. }
  361. stack.unshift(...marker.children);
  362. }
  363. }
  364. export class TextmateSnippet extends Marker {
  365. get placeholderInfo() {
  366. if (!this._placeholders) {
  367. // fill in placeholders
  368. let all = [];
  369. let last;
  370. this.walk(function (candidate) {
  371. if (candidate instanceof Placeholder) {
  372. all.push(candidate);
  373. last = !last || last.index < candidate.index ? candidate : last;
  374. }
  375. return true;
  376. });
  377. this._placeholders = { all, last };
  378. }
  379. return this._placeholders;
  380. }
  381. get placeholders() {
  382. const { all } = this.placeholderInfo;
  383. return all;
  384. }
  385. offset(marker) {
  386. let pos = 0;
  387. let found = false;
  388. this.walk(candidate => {
  389. if (candidate === marker) {
  390. found = true;
  391. return false;
  392. }
  393. pos += candidate.len();
  394. return true;
  395. });
  396. if (!found) {
  397. return -1;
  398. }
  399. return pos;
  400. }
  401. fullLen(marker) {
  402. let ret = 0;
  403. walk([marker], marker => {
  404. ret += marker.len();
  405. return true;
  406. });
  407. return ret;
  408. }
  409. enclosingPlaceholders(placeholder) {
  410. let ret = [];
  411. let { parent } = placeholder;
  412. while (parent) {
  413. if (parent instanceof Placeholder) {
  414. ret.push(parent);
  415. }
  416. parent = parent.parent;
  417. }
  418. return ret;
  419. }
  420. resolveVariables(resolver) {
  421. this.walk(candidate => {
  422. if (candidate instanceof Variable) {
  423. if (candidate.resolve(resolver)) {
  424. this._placeholders = undefined;
  425. }
  426. }
  427. return true;
  428. });
  429. return this;
  430. }
  431. appendChild(child) {
  432. this._placeholders = undefined;
  433. return super.appendChild(child);
  434. }
  435. replace(child, others) {
  436. this._placeholders = undefined;
  437. return super.replace(child, others);
  438. }
  439. clone() {
  440. let ret = new TextmateSnippet();
  441. this._children = this.children.map(child => child.clone());
  442. return ret;
  443. }
  444. walk(visitor) {
  445. walk(this.children, visitor);
  446. }
  447. }
  448. export class SnippetParser {
  449. constructor() {
  450. this._scanner = new Scanner();
  451. this._token = { type: 14 /* EOF */, pos: 0, len: 0 };
  452. }
  453. static escape(value) {
  454. return value.replace(/\$|}|\\/g, '\\$&');
  455. }
  456. static guessNeedsClipboard(template) {
  457. return /\${?CLIPBOARD/.test(template);
  458. }
  459. parse(value, insertFinalTabstop, enforceFinalTabstop) {
  460. this._scanner.text(value);
  461. this._token = this._scanner.next();
  462. const snippet = new TextmateSnippet();
  463. while (this._parse(snippet)) {
  464. // nothing
  465. }
  466. // fill in values for placeholders. the first placeholder of an index
  467. // that has a value defines the value for all placeholders with that index
  468. const placeholderDefaultValues = new Map();
  469. const incompletePlaceholders = [];
  470. let placeholderCount = 0;
  471. snippet.walk(marker => {
  472. if (marker instanceof Placeholder) {
  473. placeholderCount += 1;
  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. if (!enforceFinalTabstop) {
  498. enforceFinalTabstop = placeholderCount > 0 && insertFinalTabstop;
  499. }
  500. if (!placeholderDefaultValues.has(0) && enforceFinalTabstop) {
  501. // the snippet uses placeholders but has no
  502. // final tabstop defined -> insert at the end
  503. snippet.appendChild(new Placeholder(0));
  504. }
  505. return snippet;
  506. }
  507. _accept(type, value) {
  508. if (type === undefined || this._token.type === type) {
  509. let ret = !value ? true : this._scanner.tokenText(this._token);
  510. this._token = this._scanner.next();
  511. return ret;
  512. }
  513. return false;
  514. }
  515. _backTo(token) {
  516. this._scanner.pos = token.pos + token.len;
  517. this._token = token;
  518. return false;
  519. }
  520. _until(type) {
  521. const start = this._token;
  522. while (this._token.type !== type) {
  523. if (this._token.type === 14 /* EOF */) {
  524. return false;
  525. }
  526. else if (this._token.type === 5 /* Backslash */) {
  527. const nextToken = this._scanner.next();
  528. if (nextToken.type !== 0 /* Dollar */
  529. && nextToken.type !== 4 /* CurlyClose */
  530. && nextToken.type !== 5 /* Backslash */) {
  531. return false;
  532. }
  533. }
  534. this._token = this._scanner.next();
  535. }
  536. const value = this._scanner.value.substring(start.pos, this._token.pos).replace(/\\(\$|}|\\)/g, '$1');
  537. this._token = this._scanner.next();
  538. return value;
  539. }
  540. _parse(marker) {
  541. return this._parseEscaped(marker)
  542. || this._parseTabstopOrVariableName(marker)
  543. || this._parseComplexPlaceholder(marker)
  544. || this._parseComplexVariable(marker)
  545. || this._parseAnything(marker);
  546. }
  547. // \$, \\, \} -> just text
  548. _parseEscaped(marker) {
  549. let value;
  550. if (value = this._accept(5 /* Backslash */, true)) {
  551. // saw a backslash, append escaped token or that backslash
  552. value = this._accept(0 /* Dollar */, true)
  553. || this._accept(4 /* CurlyClose */, true)
  554. || this._accept(5 /* Backslash */, true)
  555. || value;
  556. marker.appendChild(new Text(value));
  557. return true;
  558. }
  559. return false;
  560. }
  561. // $foo -> variable, $1 -> tabstop
  562. _parseTabstopOrVariableName(parent) {
  563. let value;
  564. const token = this._token;
  565. const match = this._accept(0 /* Dollar */)
  566. && (value = this._accept(9 /* VariableName */, true) || this._accept(8 /* Int */, true));
  567. if (!match) {
  568. return this._backTo(token);
  569. }
  570. parent.appendChild(/^\d+$/.test(value)
  571. ? new Placeholder(Number(value))
  572. : new Variable(value));
  573. return true;
  574. }
  575. // ${1:<children>}, ${1} -> placeholder
  576. _parseComplexPlaceholder(parent) {
  577. let index;
  578. const token = this._token;
  579. const match = this._accept(0 /* Dollar */)
  580. && this._accept(3 /* CurlyOpen */)
  581. && (index = this._accept(8 /* Int */, true));
  582. if (!match) {
  583. return this._backTo(token);
  584. }
  585. const placeholder = new Placeholder(Number(index));
  586. if (this._accept(1 /* Colon */)) {
  587. // ${1:<children>}
  588. while (true) {
  589. // ...} -> done
  590. if (this._accept(4 /* CurlyClose */)) {
  591. parent.appendChild(placeholder);
  592. return true;
  593. }
  594. if (this._parse(placeholder)) {
  595. continue;
  596. }
  597. // fallback
  598. parent.appendChild(new Text('${' + index + ':'));
  599. placeholder.children.forEach(parent.appendChild, parent);
  600. return true;
  601. }
  602. }
  603. else if (placeholder.index > 0 && this._accept(7 /* Pipe */)) {
  604. // ${1|one,two,three|}
  605. const choice = new Choice();
  606. while (true) {
  607. if (this._parseChoiceElement(choice)) {
  608. if (this._accept(2 /* Comma */)) {
  609. // opt, -> more
  610. continue;
  611. }
  612. if (this._accept(7 /* Pipe */)) {
  613. placeholder.appendChild(choice);
  614. if (this._accept(4 /* CurlyClose */)) {
  615. // ..|} -> done
  616. parent.appendChild(placeholder);
  617. return true;
  618. }
  619. }
  620. }
  621. this._backTo(token);
  622. return false;
  623. }
  624. }
  625. else if (this._accept(6 /* Forwardslash */)) {
  626. // ${1/<regex>/<format>/<options>}
  627. if (this._parseTransform(placeholder)) {
  628. parent.appendChild(placeholder);
  629. return true;
  630. }
  631. this._backTo(token);
  632. return false;
  633. }
  634. else if (this._accept(4 /* CurlyClose */)) {
  635. // ${1}
  636. parent.appendChild(placeholder);
  637. return true;
  638. }
  639. else {
  640. // ${1 <- missing curly or colon
  641. return this._backTo(token);
  642. }
  643. }
  644. _parseChoiceElement(parent) {
  645. const token = this._token;
  646. const values = [];
  647. while (true) {
  648. if (this._token.type === 2 /* Comma */ || this._token.type === 7 /* Pipe */) {
  649. break;
  650. }
  651. let value;
  652. if (value = this._accept(5 /* Backslash */, true)) {
  653. // \, \|, or \\
  654. value = this._accept(2 /* Comma */, true)
  655. || this._accept(7 /* Pipe */, true)
  656. || this._accept(5 /* Backslash */, true)
  657. || value;
  658. }
  659. else {
  660. value = this._accept(undefined, true);
  661. }
  662. if (!value) {
  663. // EOF
  664. this._backTo(token);
  665. return false;
  666. }
  667. values.push(value);
  668. }
  669. if (values.length === 0) {
  670. this._backTo(token);
  671. return false;
  672. }
  673. parent.appendChild(new Text(values.join('')));
  674. return true;
  675. }
  676. // ${foo:<children>}, ${foo} -> variable
  677. _parseComplexVariable(parent) {
  678. let name;
  679. const token = this._token;
  680. const match = this._accept(0 /* Dollar */)
  681. && this._accept(3 /* CurlyOpen */)
  682. && (name = this._accept(9 /* VariableName */, true));
  683. if (!match) {
  684. return this._backTo(token);
  685. }
  686. const variable = new Variable(name);
  687. if (this._accept(1 /* Colon */)) {
  688. // ${foo:<children>}
  689. while (true) {
  690. // ...} -> done
  691. if (this._accept(4 /* CurlyClose */)) {
  692. parent.appendChild(variable);
  693. return true;
  694. }
  695. if (this._parse(variable)) {
  696. continue;
  697. }
  698. // fallback
  699. parent.appendChild(new Text('${' + name + ':'));
  700. variable.children.forEach(parent.appendChild, parent);
  701. return true;
  702. }
  703. }
  704. else if (this._accept(6 /* Forwardslash */)) {
  705. // ${foo/<regex>/<format>/<options>}
  706. if (this._parseTransform(variable)) {
  707. parent.appendChild(variable);
  708. return true;
  709. }
  710. this._backTo(token);
  711. return false;
  712. }
  713. else if (this._accept(4 /* CurlyClose */)) {
  714. // ${foo}
  715. parent.appendChild(variable);
  716. return true;
  717. }
  718. else {
  719. // ${foo <- missing curly or colon
  720. return this._backTo(token);
  721. }
  722. }
  723. _parseTransform(parent) {
  724. // ...<regex>/<format>/<options>}
  725. let transform = new Transform();
  726. let regexValue = '';
  727. let regexOptions = '';
  728. // (1) /regex
  729. while (true) {
  730. if (this._accept(6 /* Forwardslash */)) {
  731. break;
  732. }
  733. let escaped;
  734. if (escaped = this._accept(5 /* Backslash */, true)) {
  735. escaped = this._accept(6 /* Forwardslash */, true) || escaped;
  736. regexValue += escaped;
  737. continue;
  738. }
  739. if (this._token.type !== 14 /* EOF */) {
  740. regexValue += this._accept(undefined, true);
  741. continue;
  742. }
  743. return false;
  744. }
  745. // (2) /format
  746. while (true) {
  747. if (this._accept(6 /* Forwardslash */)) {
  748. break;
  749. }
  750. let escaped;
  751. if (escaped = this._accept(5 /* Backslash */, true)) {
  752. escaped = this._accept(5 /* Backslash */, true) || this._accept(6 /* Forwardslash */, true) || escaped;
  753. transform.appendChild(new Text(escaped));
  754. continue;
  755. }
  756. if (this._parseFormatString(transform) || this._parseAnything(transform)) {
  757. continue;
  758. }
  759. return false;
  760. }
  761. // (3) /option
  762. while (true) {
  763. if (this._accept(4 /* CurlyClose */)) {
  764. break;
  765. }
  766. if (this._token.type !== 14 /* EOF */) {
  767. regexOptions += this._accept(undefined, true);
  768. continue;
  769. }
  770. return false;
  771. }
  772. try {
  773. transform.regexp = new RegExp(regexValue, regexOptions);
  774. }
  775. catch (e) {
  776. // invalid regexp
  777. return false;
  778. }
  779. parent.transform = transform;
  780. return true;
  781. }
  782. _parseFormatString(parent) {
  783. const token = this._token;
  784. if (!this._accept(0 /* Dollar */)) {
  785. return false;
  786. }
  787. let complex = false;
  788. if (this._accept(3 /* CurlyOpen */)) {
  789. complex = true;
  790. }
  791. let index = this._accept(8 /* Int */, true);
  792. if (!index) {
  793. this._backTo(token);
  794. return false;
  795. }
  796. else if (!complex) {
  797. // $1
  798. parent.appendChild(new FormatString(Number(index)));
  799. return true;
  800. }
  801. else if (this._accept(4 /* CurlyClose */)) {
  802. // ${1}
  803. parent.appendChild(new FormatString(Number(index)));
  804. return true;
  805. }
  806. else if (!this._accept(1 /* Colon */)) {
  807. this._backTo(token);
  808. return false;
  809. }
  810. if (this._accept(6 /* Forwardslash */)) {
  811. // ${1:/upcase}
  812. let shorthand = this._accept(9 /* VariableName */, true);
  813. if (!shorthand || !this._accept(4 /* CurlyClose */)) {
  814. this._backTo(token);
  815. return false;
  816. }
  817. else {
  818. parent.appendChild(new FormatString(Number(index), shorthand));
  819. return true;
  820. }
  821. }
  822. else if (this._accept(11 /* Plus */)) {
  823. // ${1:+<if>}
  824. let ifValue = this._until(4 /* CurlyClose */);
  825. if (ifValue) {
  826. parent.appendChild(new FormatString(Number(index), undefined, ifValue, undefined));
  827. return true;
  828. }
  829. }
  830. else if (this._accept(12 /* Dash */)) {
  831. // ${2:-<else>}
  832. let elseValue = this._until(4 /* CurlyClose */);
  833. if (elseValue) {
  834. parent.appendChild(new FormatString(Number(index), undefined, undefined, elseValue));
  835. return true;
  836. }
  837. }
  838. else if (this._accept(13 /* QuestionMark */)) {
  839. // ${2:?<if>:<else>}
  840. let ifValue = this._until(1 /* Colon */);
  841. if (ifValue) {
  842. let elseValue = this._until(4 /* CurlyClose */);
  843. if (elseValue) {
  844. parent.appendChild(new FormatString(Number(index), undefined, ifValue, elseValue));
  845. return true;
  846. }
  847. }
  848. }
  849. else {
  850. // ${1:<else>}
  851. let elseValue = this._until(4 /* CurlyClose */);
  852. if (elseValue) {
  853. parent.appendChild(new FormatString(Number(index), undefined, undefined, elseValue));
  854. return true;
  855. }
  856. }
  857. this._backTo(token);
  858. return false;
  859. }
  860. _parseAnything(marker) {
  861. if (this._token.type !== 14 /* EOF */) {
  862. marker.appendChild(new Text(this._scanner.tokenText(this._token)));
  863. this._accept(undefined);
  864. return true;
  865. }
  866. return false;
  867. }
  868. }