Torkel Ödegaard 12 лет назад
Родитель
Сommit
35ae9e3379

+ 6 - 2
src/app/services/graphite/lexer.js

@@ -185,10 +185,14 @@ define([
         }
         }
       }
       }
 
 
-      var match =
+      var match = this.scanStringLiteral();
+      if (match) {
+        return match;
+      }
+
+      match =
         this.scanIdentifier() ||
         this.scanIdentifier() ||
         this.scanPunctuator() ||
         this.scanPunctuator() ||
-        this.scanStringLiteral() ||
         this.scanNumericLiteral();
         this.scanNumericLiteral();
 
 
       if (match) {
       if (match) {

+ 126 - 87
src/app/services/graphite/parser.js

@@ -3,9 +3,11 @@ define([
 ], function (Lexer) {
 ], function (Lexer) {
 
 
   var NodeTypes = {
   var NodeTypes = {
-    MetricExpression = 1,
+    MetricExpression: 1,
     MetricNode: 2,
     MetricNode: 2,
-    FunctionCall: 4
+    FunctionCall: 4,
+    NumericLiteral: 5,
+    StringLiteral: 6
   };
   };
 
 
   function Node(type, value) {
   function Node(type, value) {
@@ -20,147 +22,184 @@ define([
     this.error = null;
     this.error = null;
   }
   }
 
 
+  Parser.Nodes = NodeTypes;
+
   Parser.prototype = {
   Parser.prototype = {
       getAst: function () {
       getAst: function () {
-        return parse('start');
+        return this.parse('start');
       },
       },
 
 
-      checkToken: function (token, expected) {
-        if (token === null) {
+      isUnexpectedToken: function (expected, value) {
+        if (this.token === null) {
           this.error = "Expected token: " + expected + " instead found end of string";
           this.error = "Expected token: " + expected + " instead found end of string";
-          return;
-        }
-
-        if (token.type === expected) {
           return true;
           return true;
         }
         }
 
 
-        this.error = "Expected  token "
-            + expected + " instead found + "
-            found + " at position: " + lexer.char;
-
-        return false;
-      },
-
-      parse: function (state) {
-        var node = { children: [] };
-
-        var token = lexer.next();
-        var nextToken = lexer.next();
-
-        if (checkToken(token, Lexer.Token.Identifier) {
-          return null;
+        if (this.token.type === expected) {
+          return false;
         }
         }
 
 
-        if (nextToken == null) {
-          return {
-            type: NodeTypes.MetricExpression,
-            nodes: [
-              {
-                type: NodeTypes.MetricNode,
-                value: token.value
-              }
-            ]
-          }
+        if (value && this.token.value === value) {
+          return false;
         }
         }
 
 
-        if (checkToken(nextToken, Lexer.Token.Punctuator)) {
-          return null;
-        }
+        this.error = "Expected  token " + expected +
+            ' instead found token ' + this.token.type +
+            ' ("'  + this.token.value + '")' +
+            " at position: " + this.lexer.char;
 
 
-        if (nextToken.value === '.') {
-          return parseMetricExpression(token);
-        }
+        return true;
       },
       },
 
 
-      parseMetricExpression: function(firstToken) {
-        var node = {
-          type: NodeTypes.MetricExpression,
-          nodes: [
-            {
-              type: NodeTypes.MetricNode,
-              value: firstToken.value
-            }
-          ]
-        };
-
-        var token;
+      parse: function (state, allowParams) {
+        var node = { };
 
 
         while(true) {
         while(true) {
-          token = lexer.nextToken();
-          if (checkToken(token, Lexer.Token.Identifier)) {
-            return null;
-          }
-
-        }
-      }
-        /*while(true) {
-          token = lexer.next();
+          this.token = this.lexer.next();
 
 
           switch(state) {
           switch(state) {
-
           case "start":
           case "start":
-            if (checkToken(token, Lexer.Token.Identifier) {
+            if (allowParams) {
+              if (this.token === null) {
+                return null;
+              }
+
+              if (this.token.type === Lexer.Token.NumericLiteral) {
+                return {
+                  type: NodeTypes.NumericLiteral,
+                  value: parseInt(this.token.value)
+                };
+              }
+
+              if (this.token.type === Lexer.Token.StringLiteral) {
+                return {
+                  type: NodeTypes.StringLiteral,
+                  value: this.token.value
+                };
+              }
+            }
+
+            if (this.isUnexpectedToken(Lexer.Token.Identifier)) {
               return;
               return;
             }
             }
 
 
             state = "identifier";
             state = "identifier";
-            prevToken = token;
+            this.prevToken = this.token;
             break;
             break;
 
 
           case "identifier":
           case "identifier":
-            if (token == null) {
-              node.type = NodeTypes.MetricExpression;
-              node.children.push([
-                type: NodeTypes.MetricNode,
-                value: prevToken.value;
-              ]);
-
-              return node;
+            if (this.token == null || (allowParams && this.token.value === ',')) {
+              return {
+                type: NodeTypes.MetricExpression,
+                segments: [{
+                    type: NodeTypes.MetricExpression,
+                    value: this.prevToken.value
+                }]
+              };
             }
             }
 
 
-            if (checkToken(token, Lexer.Token.Punctuator)) {
-              return;
+            if (this.isUnexpectedToken(Lexer.Token.Punctuator)) {
+              return null;
             }
             }
 
 
-            if (token.value === '.') {
+            if (this.token.value === '.') {
               state = "metricNode";
               state = "metricNode";
               node.type = NodeTypes.MetricExpression;
               node.type = NodeTypes.MetricExpression;
-              node.children.push({
+              node.segments = [{
                 type: NodeTypes.MetricNode,
                 type: NodeTypes.MetricNode,
-                value: prevToken.value
-              });
+                value: this.prevToken.value
+              }];
+
+              continue;
             }
             }
 
 
-            if (token.value === '(') {
-              state = 'function';
+            if (this.token.value === '(') {
+              node.type = NodeTypes.FunctionCall;
+              node.name = this.prevToken.value;
+              node.params = this.parseFunc();
+              return node;
+            }
+
+            if (this.token.value === ')') {
+              return node;
             }
             }
 
 
             break;
             break;
+
           case 'metricEnd':
           case 'metricEnd':
-            if (token === null) {
+            if (this.token === null) {
               return node;
               return node;
             }
             }
 
 
-            if (checkToken(token, Lexer.Token.Punctuator)) {
+            if (this.isUnexpectedToken(Lexer.Token.Punctuator)) {
               return null;
               return null;
             }
             }
 
 
+            if (this.token.value === '.') {
+              state = 'metricNode';
+            }
+
+            if (allowParams && (this.token.value === ',' || this.token.value === ')')) {
+              return node;
+            }
+
+            break;
           case 'metricNode':
           case 'metricNode':
-            if (checkToken(token, Lexer.Token.Identifier)) {
+            if (this.isUnexpectedToken(Lexer.Token.Identifier)) {
               return null;
               return null;
             }
             }
 
 
-            node.children.push([
+            node.segments.push({
               type: NodeTypes.MetricNode,
               type: NodeTypes.MetricNode,
-              value: token.value
-            ]);
+              value: this.token.value
+            });
 
 
             state = 'metricEnd';
             state = 'metricEnd';
             break;
             break;
+          default:
+            this.error = 'unknown token: ' + this.token.type;
           }
           }
         }
         }
-      }*/
+      },
+
+      parseFunc: function() {
+        var arguments = [];
+        var arg;
+
+        while(true) {
+
+          arg = this.parse('start', true);
+          if (arg === null) {
+            this.error = "expected function arguments";
+            return null;
+          }
+
+          arguments.push(arg);
+
+          if (this.token === null) {
+            this.error = "expected closing function at position: " + this.lexer.char;
+            return null;
+          }
+
+          if (this.token.value === ')') {
+            return arguments;
+          }
+
+          if (this.token.type === Lexer.Token.NumericLiteral ||
+              this.token.type === Lexer.Token.StringLiteral) {
+            this.token = this.lexer.next();
+          }
+
+          if (this.isUnexpectedToken(Lexer.Token.Punctuator, ',')) {
+            return null;
+          }
+
+          if (this.token.value === ')') {
+            return arguments;
+          }
+        }
+
+      }
   };
   };
 
 
   return Parser;
   return Parser;

+ 1 - 0
src/test/specs/lexer-specs.js

@@ -26,6 +26,7 @@ define([
       expect(tokens[6].value).to.be('12');
       expect(tokens[6].value).to.be('12');
       expect(tokens[8].type).to.be(Lexer.Token.StringLiteral);
       expect(tokens[8].type).to.be(Lexer.Token.StringLiteral);
       expect(tokens[8].value).to.be('test');
       expect(tokens[8].value).to.be('test');
+      expect(tokens[tokens.length - 1].value).to.be(')');
     });
     });
 
 
   });
   });

+ 51 - 5
src/test/specs/parser-specs.js

@@ -4,15 +4,61 @@ define([
 
 
   describe('when parsing graphite expression', function() {
   describe('when parsing graphite expression', function() {
 
 
-    it('should return ast', function() {
+    it('simple metric expression', function() {
       var parser = new Parser('metric.test.*.asd.count');
       var parser = new Parser('metric.test.*.asd.count');
-      var ast = parser.getAst();
-      expect(ast[0].type).to.be(Parser.Nodes.MetricExpression);
-      expect(ast[0].nodes.length).to.be(5);
-      expect(ast[0].nodes[0].value).to.be('metric');
+      var rootNode = parser.getAst();
 
 
+      expect(parser.error).to.be(null);
+      expect(rootNode.type).to.be(Parser.Nodes.MetricExpression);
+      expect(rootNode.segments.length).to.be(5);
+      expect(rootNode.segments[0].value).to.be('metric');
+
+    });
+
+    it('simple function', function() {
+      var parser = new Parser('sum(test)');
+      var rootNode = parser.getAst();
+      expect(parser.error).to.be(null);
+      expect(rootNode.type).to.be(Parser.Nodes.FunctionCall);
+      expect(rootNode.params.length).to.be(1);
+    });
+
+    it('function with multiple args', function() {
+      var parser = new Parser("sum(test, 1, 'test')");
+      var rootNode = parser.getAst();
+
+      expect(parser.error).to.be(null);
+      expect(rootNode.type).to.be(Parser.Nodes.FunctionCall);
+      expect(rootNode.params.length).to.be(3);
+      expect(rootNode.params[0].type).to.be(Parser.Nodes.MetricExpression);
+      expect(rootNode.params[1].type).to.be(Parser.Nodes.NumericLiteral);
+      expect(rootNode.params[2].type).to.be(Parser.Nodes.StringLiteral);
     });
     });
 
 
+    it('function with nested function', function() {
+      var parser = new Parser("sum(scaleToSeconds(test, 1))");
+      var rootNode = parser.getAst();
+
+      expect(parser.error).to.be(null);
+      expect(rootNode.type).to.be(Parser.Nodes.FunctionCall);
+      expect(rootNode.params.length).to.be(1);
+      expect(rootNode.params[0].type).to.be(Parser.Nodes.FunctionCall);
+      expect(rootNode.params[0].name).to.be('scaleToSeconds');
+      expect(rootNode.params[0].params.length).to.be(2);
+      expect(rootNode.params[0].params[0].type).to.be(Parser.Nodes.MetricExpression);
+      expect(rootNode.params[0].params[1].type).to.be(Parser.Nodes.NumericLiteral);
+    });
+
+    it('function with multiple series', function() {
+      var parser = new Parser("sum(test.test.*.count, test.timers.*.count)");
+      var rootNode = parser.getAst();
+
+      expect(parser.error).to.be(null);
+      expect(rootNode.type).to.be(Parser.Nodes.FunctionCall);
+      expect(rootNode.params.length).to.be(2);
+      expect(rootNode.params[0].type).to.be(Parser.Nodes.MetricExpression);
+      expect(rootNode.params[1].type).to.be(Parser.Nodes.MetricExpression);
+    });
 
 
   });
   });