grafana/public/app/plugins/datasource/graphite/parser.ts

262 lines
5.3 KiB
TypeScript
Raw Normal View History

import { Lexer } from "./lexer";
export function Parser(expression) {
this.expression = expression;
this.lexer = new Lexer(expression);
this.tokens = this.lexer.tokenize();
this.index = 0;
}
Parser.prototype = {
getAst: function() {
return this.start();
},
start: function() {
try {
return this.functionCall() || this.metricExpression();
} catch (e) {
return {
type: "error",
message: e.message,
pos: e.pos
};
}
},
curlyBraceSegment: function() {
if (this.match("identifier", "{") || this.match("{")) {
var curlySegment = "";
while (!this.match("") && !this.match("}")) {
curlySegment += this.consumeToken().value;
}
if (!this.match("}")) {
this.errorMark("Expected closing '}'");
}
curlySegment += this.consumeToken().value;
// if curly segment is directly followed by identifier
// include it in the segment
if (this.match("identifier")) {
curlySegment += this.consumeToken().value;
}
return {
type: "segment",
value: curlySegment
};
} else {
return null;
}
},
metricSegment: function() {
var curly = this.curlyBraceSegment();
if (curly) {
return curly;
}
if (this.match("identifier") || this.match("number")) {
// hack to handle float numbers in metric segments
var parts = this.consumeToken().value.split(".");
if (parts.length === 2) {
this.tokens.splice(this.index, 0, { type: "." });
this.tokens.splice(this.index + 1, 0, {
type: "number",
value: parts[1]
});
}
return {
type: "segment",
value: parts[0]
};
}
if (!this.match("templateStart")) {
this.errorMark("Expected metric identifier");
}
this.consumeToken();
if (!this.match("identifier")) {
this.errorMark("Expected identifier after templateStart");
}
var node = {
type: "template",
value: this.consumeToken().value
};
if (!this.match("templateEnd")) {
this.errorMark("Expected templateEnd");
}
this.consumeToken();
return node;
},
metricExpression: function() {
if (
!this.match("templateStart") &&
!this.match("identifier") &&
!this.match("number") &&
!this.match("{")
) {
return null;
}
var node = {
type: "metric",
segments: []
};
node.segments.push(this.metricSegment());
while (this.match(".")) {
this.consumeToken();
var segment = this.metricSegment();
if (!segment) {
this.errorMark("Expected metric identifier");
}
node.segments.push(segment);
}
return node;
},
functionCall: function() {
if (!this.match("identifier", "(")) {
return null;
}
var node: any = {
type: "function",
name: this.consumeToken().value
};
// consume left parenthesis
this.consumeToken();
node.params = this.functionParameters();
if (!this.match(")")) {
this.errorMark("Expected closing parenthesis");
}
this.consumeToken();
return node;
},
boolExpression: function() {
if (!this.match("bool")) {
return null;
}
return {
type: "bool",
value: this.consumeToken().value === "true"
};
},
functionParameters: function() {
if (this.match(")") || this.match("")) {
return [];
}
var param =
this.functionCall() ||
this.numericLiteral() ||
this.seriesRefExpression() ||
this.boolExpression() ||
this.metricExpression() ||
this.stringLiteral();
if (!this.match(",")) {
return [param];
}
this.consumeToken();
return [param].concat(this.functionParameters());
},
seriesRefExpression: function() {
if (!this.match("identifier")) {
return null;
}
var value = this.tokens[this.index].value;
if (!value.match(/\#[A-Z]/)) {
return null;
}
var token = this.consumeToken();
return {
type: "series-ref",
value: token.value
};
},
numericLiteral: function() {
if (!this.match("number")) {
return null;
}
return {
type: "number",
value: parseFloat(this.consumeToken().value)
};
},
stringLiteral: function() {
if (!this.match("string")) {
return null;
}
var token = this.consumeToken();
if (token.isUnclosed) {
throw { message: "Unclosed string parameter", pos: token.pos };
}
return {
type: "string",
value: token.value
};
},
errorMark: function(text) {
var currentToken = this.tokens[this.index];
var type = currentToken ? currentToken.type : "end of string";
throw {
message: text + " instead found " + type,
pos: currentToken ? currentToken.pos : this.lexer.char
};
},
// returns token value and incre
consumeToken: function() {
this.index++;
return this.tokens[this.index - 1];
},
matchToken: function(type, index) {
var token = this.tokens[this.index + index];
return (
(token === undefined && type === "") || (token && token.type === type)
);
},
match: function(token1, token2) {
return (
this.matchToken(token1, 0) && (!token2 || this.matchToken(token2, 1))
);
}
};