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

253 lines
5.3 KiB
TypeScript
Raw Normal View History

2017-12-20 05:33:33 -06:00
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 {
2017-12-20 05:33:33 -06:00
type: 'error',
message: e.message,
2017-12-20 05:33:33 -06:00
pos: e.pos,
};
}
},
curlyBraceSegment: function() {
2017-12-20 05:33:33 -06:00
if (this.match('identifier', '{') || this.match('{')) {
var curlySegment = '';
2017-12-20 05:33:33 -06:00
while (!this.match('') && !this.match('}')) {
curlySegment += this.consumeToken().value;
}
2017-12-20 05:33:33 -06:00
if (!this.match('}')) {
this.errorMark("Expected closing '}'");
}
curlySegment += this.consumeToken().value;
// if curly segment is directly followed by identifier
// include it in the segment
2017-12-20 05:33:33 -06:00
if (this.match('identifier')) {
curlySegment += this.consumeToken().value;
}
return {
2017-12-20 05:33:33 -06:00
type: 'segment',
value: curlySegment,
};
} else {
return null;
}
},
metricSegment: function() {
2018-08-29 07:26:50 -05:00
const curly = this.curlyBraceSegment();
if (curly) {
return curly;
}
2017-12-20 05:33:33 -06:00
if (this.match('identifier') || this.match('number')) {
// hack to handle float numbers in metric segments
2018-08-29 07:26:50 -05:00
const parts = this.consumeToken().value.split('.');
if (parts.length === 2) {
2017-12-20 05:33:33 -06:00
this.tokens.splice(this.index, 0, { type: '.' });
this.tokens.splice(this.index + 1, 0, {
2017-12-20 05:33:33 -06:00
type: 'number',
value: parts[1],
});
}
return {
2017-12-20 05:33:33 -06:00
type: 'segment',
value: parts[0],
};
}
2017-12-20 05:33:33 -06:00
if (!this.match('templateStart')) {
this.errorMark('Expected metric identifier');
}
this.consumeToken();
2017-12-20 05:33:33 -06:00
if (!this.match('identifier')) {
this.errorMark('Expected identifier after templateStart');
}
2018-08-29 07:26:50 -05:00
const node = {
2017-12-20 05:33:33 -06:00
type: 'template',
value: this.consumeToken().value,
};
2017-12-20 05:33:33 -06:00
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;
}
2018-08-29 07:26:50 -05:00
const node = {
2017-12-20 05:33:33 -06:00
type: 'metric',
segments: [],
};
node.segments.push(this.metricSegment());
2017-12-20 05:33:33 -06:00
while (this.match('.')) {
this.consumeToken();
2018-08-29 07:26:50 -05:00
const segment = this.metricSegment();
if (!segment) {
2017-12-20 05:33:33 -06:00
this.errorMark('Expected metric identifier');
}
node.segments.push(segment);
}
return node;
},
functionCall: function() {
2017-12-20 05:33:33 -06:00
if (!this.match('identifier', '(')) {
return null;
}
2018-08-29 07:26:50 -05:00
const node: any = {
2017-12-20 05:33:33 -06:00
type: 'function',
name: this.consumeToken().value,
};
// consume left parenthesis
this.consumeToken();
node.params = this.functionParameters();
2017-12-20 05:33:33 -06:00
if (!this.match(')')) {
this.errorMark('Expected closing parenthesis');
}
this.consumeToken();
return node;
},
boolExpression: function() {
2017-12-20 05:33:33 -06:00
if (!this.match('bool')) {
return null;
}
return {
2017-12-20 05:33:33 -06:00
type: 'bool',
value: this.consumeToken().value === 'true',
};
},
functionParameters: function() {
2017-12-20 05:33:33 -06:00
if (this.match(')') || this.match('')) {
return [];
}
2018-08-29 07:26:50 -05:00
const param =
this.functionCall() ||
this.numericLiteral() ||
this.seriesRefExpression() ||
this.boolExpression() ||
this.metricExpression() ||
this.stringLiteral();
2017-12-20 05:33:33 -06:00
if (!this.match(',')) {
return [param];
}
this.consumeToken();
return [param].concat(this.functionParameters());
},
seriesRefExpression: function() {
2017-12-20 05:33:33 -06:00
if (!this.match('identifier')) {
return null;
}
2018-08-29 07:26:50 -05:00
const value = this.tokens[this.index].value;
if (!value.match(/\#[A-Z]/)) {
return null;
}
2018-08-29 07:26:50 -05:00
const token = this.consumeToken();
return {
2017-12-20 05:33:33 -06:00
type: 'series-ref',
value: token.value,
};
},
numericLiteral: function() {
2017-12-20 05:33:33 -06:00
if (!this.match('number')) {
return null;
}
return {
2017-12-20 05:33:33 -06:00
type: 'number',
value: parseFloat(this.consumeToken().value),
};
},
stringLiteral: function() {
2017-12-20 05:33:33 -06:00
if (!this.match('string')) {
return null;
}
2018-08-29 07:26:50 -05:00
const token = this.consumeToken();
if (token.isUnclosed) {
2017-12-20 05:33:33 -06:00
throw { message: 'Unclosed string parameter', pos: token.pos };
}
return {
2017-12-20 05:33:33 -06:00
type: 'string',
value: token.value,
};
},
errorMark: function(text) {
2018-08-29 07:26:50 -05:00
const currentToken = this.tokens[this.index];
const type = currentToken ? currentToken.type : 'end of string';
throw {
2017-12-20 05:33:33 -06:00
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) {
2018-08-29 07:26:50 -05:00
const 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));
2017-12-20 05:33:33 -06:00
},
};