diff --git a/src/app/services/graphite/lexer.js b/src/app/services/graphite/lexer.js index db97caf3095..b57e7755565 100644 --- a/src/app/services/graphite/lexer.js +++ b/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.scanPunctuator() || - this.scanStringLiteral() || this.scanNumericLiteral(); if (match) { diff --git a/src/app/services/graphite/parser.js b/src/app/services/graphite/parser.js index d4dd09d29d1..928e1715cec 100644 --- a/src/app/services/graphite/parser.js +++ b/src/app/services/graphite/parser.js @@ -3,9 +3,11 @@ define([ ], function (Lexer) { var NodeTypes = { - MetricExpression = 1, + MetricExpression: 1, MetricNode: 2, - FunctionCall: 4 + FunctionCall: 4, + NumericLiteral: 5, + StringLiteral: 6 }; function Node(type, value) { @@ -20,147 +22,184 @@ define([ this.error = null; } + Parser.Nodes = NodeTypes; + Parser.prototype = { 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"; - return; - } - - if (token.type === expected) { return true; } - this.error = "Expected token " - + expected + " instead found + " - found + " at position: " + lexer.char; + if (this.token.type === expected) { + return false; + } - return false; + if (value && this.token.value === value) { + return false; + } + + this.error = "Expected token " + expected + + ' instead found token ' + this.token.type + + ' ("' + this.token.value + '")' + + " at position: " + this.lexer.char; + + return true; }, - parse: function (state) { - var node = { children: [] }; - - var token = lexer.next(); - var nextToken = lexer.next(); - - if (checkToken(token, Lexer.Token.Identifier) { - return null; - } - - if (nextToken == null) { - return { - type: NodeTypes.MetricExpression, - nodes: [ - { - type: NodeTypes.MetricNode, - value: token.value - } - ] - } - } - - if (checkToken(nextToken, Lexer.Token.Punctuator)) { - return null; - } - - if (nextToken.value === '.') { - return parseMetricExpression(token); - } - }, - - 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) { - token = lexer.nextToken(); - if (checkToken(token, Lexer.Token.Identifier)) { - return null; - } - - } - } - /*while(true) { - token = lexer.next(); + this.token = this.lexer.next(); switch(state) { - 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; } state = "identifier"; - prevToken = token; + this.prevToken = this.token; break; case "identifier": - if (token == null) { - node.type = NodeTypes.MetricExpression; - node.children.push([ - type: NodeTypes.MetricNode, - value: prevToken.value; - ]); + if (this.token == null || (allowParams && this.token.value === ',')) { + return { + type: NodeTypes.MetricExpression, + segments: [{ + type: NodeTypes.MetricExpression, + value: this.prevToken.value + }] + }; + } + if (this.isUnexpectedToken(Lexer.Token.Punctuator)) { + return null; + } + + if (this.token.value === '.') { + state = "metricNode"; + node.type = NodeTypes.MetricExpression; + node.segments = [{ + type: NodeTypes.MetricNode, + value: this.prevToken.value + }]; + + continue; + } + + if (this.token.value === '(') { + node.type = NodeTypes.FunctionCall; + node.name = this.prevToken.value; + node.params = this.parseFunc(); return node; } - if (checkToken(token, Lexer.Token.Punctuator)) { - return; - } - - if (token.value === '.') { - state = "metricNode"; - node.type = NodeTypes.MetricExpression; - node.children.push({ - type: NodeTypes.MetricNode, - value: prevToken.value - }); - } - - if (token.value === '(') { - state = 'function'; + if (this.token.value === ')') { + return node; } break; + case 'metricEnd': - if (token === null) { + if (this.token === null) { return node; } - if (checkToken(token, Lexer.Token.Punctuator)) { + if (this.isUnexpectedToken(Lexer.Token.Punctuator)) { return null; } + if (this.token.value === '.') { + state = 'metricNode'; + } + + if (allowParams && (this.token.value === ',' || this.token.value === ')')) { + return node; + } + + break; case 'metricNode': - if (checkToken(token, Lexer.Token.Identifier)) { + if (this.isUnexpectedToken(Lexer.Token.Identifier)) { return null; } - node.children.push([ + node.segments.push({ type: NodeTypes.MetricNode, - value: token.value - ]); + value: this.token.value + }); state = 'metricEnd'; 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; diff --git a/src/test/specs/lexer-specs.js b/src/test/specs/lexer-specs.js index ed2bb7d49a8..144f5bc98c6 100644 --- a/src/test/specs/lexer-specs.js +++ b/src/test/specs/lexer-specs.js @@ -26,6 +26,7 @@ define([ expect(tokens[6].value).to.be('12'); expect(tokens[8].type).to.be(Lexer.Token.StringLiteral); expect(tokens[8].value).to.be('test'); + expect(tokens[tokens.length - 1].value).to.be(')'); }); }); diff --git a/src/test/specs/parser-specs.js b/src/test/specs/parser-specs.js index b847ab4bd9e..857bf1dbfef 100644 --- a/src/test/specs/parser-specs.js +++ b/src/test/specs/parser-specs.js @@ -4,15 +4,61 @@ define([ 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 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); + }); });