parser.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import {Lexer} from './lexer';
  2. export function Parser(expression) {
  3. this.expression = expression;
  4. this.lexer = new Lexer(expression);
  5. this.tokens = this.lexer.tokenize();
  6. this.index = 0;
  7. }
  8. Parser.prototype = {
  9. getAst: function () {
  10. return this.start();
  11. },
  12. start: function () {
  13. try {
  14. return this.functionCall() || this.metricExpression();
  15. } catch (e) {
  16. return {
  17. type: 'error',
  18. message: e.message,
  19. pos: e.pos
  20. };
  21. }
  22. },
  23. curlyBraceSegment: function() {
  24. if (this.match('identifier', '{') || this.match('{')) {
  25. var curlySegment = "";
  26. while (!this.match('') && !this.match('}')) {
  27. curlySegment += this.consumeToken().value;
  28. }
  29. if (!this.match('}')) {
  30. this.errorMark("Expected closing '}'");
  31. }
  32. curlySegment += this.consumeToken().value;
  33. // if curly segment is directly followed by identifier
  34. // include it in the segment
  35. if (this.match('identifier')) {
  36. curlySegment += this.consumeToken().value;
  37. }
  38. return {
  39. type: 'segment',
  40. value: curlySegment
  41. };
  42. } else {
  43. return null;
  44. }
  45. },
  46. metricSegment: function() {
  47. var curly = this.curlyBraceSegment();
  48. if (curly) {
  49. return curly;
  50. }
  51. if (this.match('identifier') || this.match('number')) {
  52. // hack to handle float numbers in metric segments
  53. var parts = this.consumeToken().value.split('.');
  54. if (parts.length === 2) {
  55. this.tokens.splice(this.index, 0, { type: '.' });
  56. this.tokens.splice(this.index + 1, 0, { type: 'number', value: parts[1] });
  57. }
  58. return {
  59. type: 'segment',
  60. value: parts[0]
  61. };
  62. }
  63. if (!this.match('templateStart')) {
  64. this.errorMark('Expected metric identifier');
  65. }
  66. this.consumeToken();
  67. if (!this.match('identifier')) {
  68. this.errorMark('Expected identifier after templateStart');
  69. }
  70. var node = {
  71. type: 'template',
  72. value: this.consumeToken().value
  73. };
  74. if (!this.match('templateEnd')) {
  75. this.errorMark('Expected templateEnd');
  76. }
  77. this.consumeToken();
  78. return node;
  79. },
  80. metricExpression: function() {
  81. if (!this.match('templateStart') && !this.match('identifier') && !this.match('number') && !this.match('{')) {
  82. return null;
  83. }
  84. var node = {
  85. type: 'metric',
  86. segments: []
  87. };
  88. node.segments.push(this.metricSegment());
  89. while (this.match('.')) {
  90. this.consumeToken();
  91. var segment = this.metricSegment();
  92. if (!segment) {
  93. this.errorMark('Expected metric identifier');
  94. }
  95. node.segments.push(segment);
  96. }
  97. return node;
  98. },
  99. functionCall: function() {
  100. if (!this.match('identifier', '(')) {
  101. return null;
  102. }
  103. var node: any = {
  104. type: 'function',
  105. name: this.consumeToken().value,
  106. };
  107. // consume left parenthesis
  108. this.consumeToken();
  109. node.params = this.functionParameters();
  110. if (!this.match(')')) {
  111. this.errorMark('Expected closing parenthesis');
  112. }
  113. this.consumeToken();
  114. return node;
  115. },
  116. boolExpression: function() {
  117. if (!this.match('bool')) {
  118. return null;
  119. }
  120. return {
  121. type: 'bool',
  122. value: this.consumeToken().value === 'true',
  123. };
  124. },
  125. functionParameters: function () {
  126. if (this.match(')') || this.match('')) {
  127. return [];
  128. }
  129. var param =
  130. this.functionCall() ||
  131. this.numericLiteral() ||
  132. this.seriesRefExpression() ||
  133. this.boolExpression() ||
  134. this.metricExpression() ||
  135. this.stringLiteral();
  136. if (!this.match(',')) {
  137. return [param];
  138. }
  139. this.consumeToken();
  140. return [param].concat(this.functionParameters());
  141. },
  142. seriesRefExpression: function() {
  143. if (!this.match('identifier')) {
  144. return null;
  145. }
  146. var value = this.tokens[this.index].value;
  147. if (!value.match(/\#[A-Z]/)) {
  148. return null;
  149. }
  150. var token = this.consumeToken();
  151. return {
  152. type: 'series-ref',
  153. value: token.value
  154. };
  155. },
  156. numericLiteral: function () {
  157. if (!this.match('number')) {
  158. return null;
  159. }
  160. return {
  161. type: 'number',
  162. value: parseFloat(this.consumeToken().value)
  163. };
  164. },
  165. stringLiteral: function () {
  166. if (!this.match('string')) {
  167. return null;
  168. }
  169. var token = this.consumeToken();
  170. if (token.isUnclosed) {
  171. throw { message: 'Unclosed string parameter', pos: token.pos };
  172. }
  173. return {
  174. type: 'string',
  175. value: token.value
  176. };
  177. },
  178. errorMark: function(text) {
  179. var currentToken = this.tokens[this.index];
  180. var type = currentToken ? currentToken.type : 'end of string';
  181. throw {
  182. message: text + " instead found " + type,
  183. pos: currentToken ? currentToken.pos : this.lexer.char
  184. };
  185. },
  186. // returns token value and incre
  187. consumeToken: function() {
  188. this.index++;
  189. return this.tokens[this.index - 1];
  190. },
  191. matchToken: function(type, index) {
  192. var token = this.tokens[this.index + index];
  193. return (token === undefined && type === '') ||
  194. token && token.type === type;
  195. },
  196. match: function(token1, token2) {
  197. return this.matchToken(token1, 0) &&
  198. (!token2 || this.matchToken(token2, 1));
  199. },
  200. };