utils.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import {LocationRange} from 'pegjs';
  2. import {allOperators, Token, TokenResult} from './parser';
  3. /**
  4. * Used internally within treeResultLocator to stop recursion once we've
  5. * located a matched result.
  6. */
  7. class TokenResultFound extends Error {
  8. result: any;
  9. constructor(result: any) {
  10. super();
  11. this.result = result;
  12. }
  13. }
  14. /**
  15. * Used as the marker to skip token traversal in treeResultLocator
  16. */
  17. const skipTokenMarker = Symbol('Returned to skip visiting a token');
  18. type VisitorFn = (opts: {
  19. /**
  20. * Call this to return the provided value as the result of treeResultLocator
  21. */
  22. returnResult: (result: any) => TokenResultFound;
  23. /**
  24. * Return this to skip visiting any inner tokens
  25. */
  26. skipToken: typeof skipTokenMarker;
  27. /**
  28. * The token being visited
  29. */
  30. token: TokenResult<Token>;
  31. }) => null | TokenResultFound | typeof skipTokenMarker;
  32. type TreeResultLocatorOpts = {
  33. /**
  34. * The value to return when returnValue was never called and all nodes of the
  35. * search tree were visited.
  36. */
  37. noResultValue: any;
  38. /**
  39. * The tree to visit
  40. */
  41. tree: TokenResult<Token>[];
  42. /**
  43. * A function used to check if we've found the result in the node we're
  44. * visiting. May also indicate that we want to skip any further traversal of
  45. * inner nodes.
  46. */
  47. visitorTest: VisitorFn;
  48. };
  49. /**
  50. * Utility function to visit every Token node within an AST tree (in DFS order)
  51. * and apply a test method that may choose to return some value from that node.
  52. *
  53. * You must call the `returnValue` method for a result to be returned.
  54. *
  55. * When returnValue is never called and all nodes of the search tree have been
  56. * visited the noResultValue will be returned.
  57. */
  58. export function treeResultLocator<T>({
  59. tree,
  60. visitorTest,
  61. noResultValue,
  62. }: TreeResultLocatorOpts): T {
  63. const returnResult = (result: any) => new TokenResultFound(result);
  64. const nodeVisitor = (token: TokenResult<Token> | null) => {
  65. if (token === null) {
  66. return;
  67. }
  68. const result = visitorTest({token, returnResult, skipToken: skipTokenMarker});
  69. // Bubble the result back up.
  70. //
  71. // XXX: Using a throw here is a bit easier than threading the return value
  72. // back up through the recursive call tree.
  73. if (result instanceof TokenResultFound) {
  74. throw result;
  75. }
  76. // Don't traverse into any nested tokens
  77. if (result === skipTokenMarker) {
  78. return;
  79. }
  80. switch (token.type) {
  81. case Token.Filter:
  82. nodeVisitor(token.key);
  83. nodeVisitor(token.value);
  84. break;
  85. case Token.KeyExplicitTag:
  86. nodeVisitor(token.key);
  87. break;
  88. case Token.KeyAggregate:
  89. nodeVisitor(token.name);
  90. token.args && nodeVisitor(token.args);
  91. nodeVisitor(token.argsSpaceBefore);
  92. nodeVisitor(token.argsSpaceAfter);
  93. break;
  94. case Token.LogicGroup:
  95. token.inner.forEach(nodeVisitor);
  96. break;
  97. case Token.KeyAggregateArgs:
  98. token.args.forEach(v => nodeVisitor(v.value));
  99. break;
  100. case Token.ValueNumberList:
  101. case Token.ValueTextList:
  102. token.items.forEach((v: any) => nodeVisitor(v.value));
  103. break;
  104. default:
  105. }
  106. };
  107. try {
  108. tree.forEach(nodeVisitor);
  109. } catch (error) {
  110. if (error instanceof TokenResultFound) {
  111. return error.result;
  112. }
  113. throw error;
  114. }
  115. return noResultValue;
  116. }
  117. type TreeTransformerOpts = {
  118. /**
  119. * The function used to transform each node
  120. */
  121. transform: (token: TokenResult<Token>) => any;
  122. /**
  123. * The tree to transform
  124. */
  125. tree: TokenResult<Token>[];
  126. };
  127. /**
  128. * Utility function to visit every Token node within an AST tree and apply
  129. * a transform to those nodes.
  130. */
  131. export function treeTransformer({tree, transform}: TreeTransformerOpts) {
  132. const nodeVisitor = (token: TokenResult<Token> | null) => {
  133. if (token === null) {
  134. return null;
  135. }
  136. switch (token.type) {
  137. case Token.Filter:
  138. return transform({
  139. ...token,
  140. key: nodeVisitor(token.key),
  141. value: nodeVisitor(token.value),
  142. });
  143. case Token.KeyExplicitTag:
  144. return transform({
  145. ...token,
  146. key: nodeVisitor(token.key),
  147. });
  148. case Token.KeyAggregate:
  149. return transform({
  150. ...token,
  151. name: nodeVisitor(token.name),
  152. args: token.args ? nodeVisitor(token.args) : token.args,
  153. argsSpaceBefore: nodeVisitor(token.argsSpaceBefore),
  154. argsSpaceAfter: nodeVisitor(token.argsSpaceAfter),
  155. });
  156. case Token.LogicGroup:
  157. return transform({
  158. ...token,
  159. inner: token.inner.map(nodeVisitor),
  160. });
  161. case Token.KeyAggregateArgs:
  162. return transform({
  163. ...token,
  164. args: token.args.map(v => ({...v, value: nodeVisitor(v.value)})),
  165. });
  166. case Token.ValueNumberList:
  167. case Token.ValueTextList:
  168. return transform({
  169. ...token,
  170. // TODO(ts): Not sure why `v` cannot be inferred here
  171. items: token.items.map((v: any) => ({...v, value: nodeVisitor(v.value)})),
  172. });
  173. default:
  174. return transform(token);
  175. }
  176. };
  177. return tree.map(nodeVisitor);
  178. }
  179. type GetKeyNameOpts = {
  180. /**
  181. * Include arguments in aggregate key names
  182. */
  183. aggregateWithArgs?: boolean;
  184. };
  185. /**
  186. * Utility to get the string name of any type of key.
  187. */
  188. export const getKeyName = (
  189. key: TokenResult<Token.KeySimple | Token.KeyExplicitTag | Token.KeyAggregate>,
  190. options: GetKeyNameOpts = {}
  191. ) => {
  192. const {aggregateWithArgs} = options;
  193. switch (key.type) {
  194. case Token.KeySimple:
  195. return key.value;
  196. case Token.KeyExplicitTag:
  197. return key.key.value;
  198. case Token.KeyAggregate:
  199. return aggregateWithArgs
  200. ? `${key.name.value}(${key.args ? key.args.text : ''})`
  201. : key.name.value;
  202. default:
  203. return '';
  204. }
  205. };
  206. export function isWithinToken(
  207. node: {location: LocationRange} | null | undefined,
  208. position: number
  209. ) {
  210. if (!node) {
  211. return false;
  212. }
  213. return position >= node.location.start.offset && position <= node.location.end.offset;
  214. }
  215. export function isOperator(value: string) {
  216. return allOperators.some(op => op === value);
  217. }