From 88a2c6fce7e8ce4e78415d73964ecc2a9779d405 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Fri, 5 Jul 2019 16:47:21 +0200 Subject: [PATCH] Graphite: Refactor lexer and parser (#17958) --- .../app/plugins/datasource/graphite/lexer.ts | 72 ++++++++-------- .../app/plugins/datasource/graphite/parser.ts | 83 ++++++++++--------- 2 files changed, 82 insertions(+), 73 deletions(-) diff --git a/public/app/plugins/datasource/graphite/lexer.ts b/public/app/plugins/datasource/graphite/lexer.ts index ed4e3287aa2..cb1c5ef725c 100644 --- a/public/app/plugins/datasource/graphite/lexer.ts +++ b/public/app/plugins/datasource/graphite/lexer.ts @@ -898,7 +898,7 @@ const unicodeLetterTable = [ 195101, ]; -const identifierStartTable = []; +const identifierStartTable: any[] = []; for (let i = 0; i < 128; i++) { identifierStartTable[i] = @@ -922,24 +922,28 @@ for (let i = 0; i < 128; i++) { const identifierPartTable = identifierStartTable; -export function Lexer(this: any, expression) { - this.input = expression; - this.char = 1; - this.from = 1; -} +export class Lexer { + input: any; + char: number; + from: number; -Lexer.prototype = { - peek: function(i) { + constructor(expression: any) { + this.input = expression; + this.char = 1; + this.from = 1; + } + + peek(i?: number) { return this.input.charAt(i || 0); - }, + } - skip: function(i) { + skip(i?: number) { i = i || 1; this.char += i; this.input = this.input.slice(i); - }, + } - tokenize: function() { + tokenize() { const list = []; let token = this.next(); while (token) { @@ -947,9 +951,9 @@ Lexer.prototype = { token = this.next(); } return list; - }, + } - next: function() { + next() { this.from = this.char; // Move to the next non-space character. @@ -979,9 +983,9 @@ Lexer.prototype = { // No token could be matched, give up. return null; - }, + } - scanTemplateSequence: function() { + scanTemplateSequence() { if (this.peek() === '[' && this.peek(1) === '[') { return { type: 'templateStart', @@ -999,7 +1003,7 @@ Lexer.prototype = { } return null; - }, + } /* * Extract a JavaScript identifier out of the next sequence of @@ -1007,7 +1011,7 @@ Lexer.prototype = { * to Identifier this method can also produce BooleanLiteral * (true/false) and NullLiteral (null). */ - scanIdentifier: function() { + scanIdentifier() { let id = ''; let index = 0; let type, char; @@ -1020,7 +1024,7 @@ Lexer.prototype = { // Both approach and unicodeLetterTable were borrowed from // Google's Traceur. - function isUnicodeLetter(code) { + function isUnicodeLetter(code: number) { for (let i = 0; i < unicodeLetterTable.length; ) { if (code < unicodeLetterTable[i++]) { return false; @@ -1034,7 +1038,7 @@ Lexer.prototype = { return false; } - function isHexDigit(str) { + function isHexDigit(str: string) { return /^[0-9a-fA-F]$/.test(str); } @@ -1157,7 +1161,7 @@ Lexer.prototype = { value: id, pos: this.char, }; - }, + } /* * Extract a numeric literal out of the next sequence of @@ -1168,26 +1172,26 @@ Lexer.prototype = { * This method's implementation was heavily influenced by the * scanNumericLiteral function in the Esprima parser's source code. */ - scanNumericLiteral: function(): any { + scanNumericLiteral(): any { let index = 0; let value = ''; const length = this.input.length; let char = this.peek(index); let bad; - function isDecimalDigit(str) { + function isDecimalDigit(str: string) { return /^[0-9]$/.test(str); } - function isOctalDigit(str) { + function isOctalDigit(str: string) { return /^[0-7]$/.test(str); } - function isHexDigit(str) { + function isHexDigit(str: string) { return /^[0-9a-fA-F]$/.test(str); } - function isIdentifierStart(ch) { + function isIdentifierStart(ch: string) { return ch === '$' || ch === '_' || ch === '\\' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } @@ -1369,9 +1373,9 @@ Lexer.prototype = { pos: this.char, isMalformed: !isFinite(+value), }; - }, + } - isPunctuator: ch1 => { + isPunctuator(ch1: string) { switch (ch1) { case '.': case '(': @@ -1383,9 +1387,9 @@ Lexer.prototype = { } return false; - }, + } - scanPunctuator: function() { + scanPunctuator() { const ch1 = this.peek(); if (this.isPunctuator(ch1)) { @@ -1397,7 +1401,7 @@ Lexer.prototype = { } return null; - }, + } /* * Extract a string out of the next sequence of characters and/or @@ -1410,7 +1414,7 @@ Lexer.prototype = { * var str = "hello\ * world"; */ - scanStringLiteral: function() { + scanStringLiteral() { /*jshint loopfunc:true */ const quote = this.peek(); @@ -1451,5 +1455,5 @@ Lexer.prototype = { quote: quote, pos: this.char, }; - }, -}; + } +} diff --git a/public/app/plugins/datasource/graphite/parser.ts b/public/app/plugins/datasource/graphite/parser.ts index cb394c05a15..b276ee20325 100644 --- a/public/app/plugins/datasource/graphite/parser.ts +++ b/public/app/plugins/datasource/graphite/parser.ts @@ -1,18 +1,23 @@ import { Lexer } from './lexer'; -export function Parser(this: any, expression) { - this.expression = expression; - this.lexer = new Lexer(expression); - this.tokens = this.lexer.tokenize(); - this.index = 0; -} +export class Parser { + expression: any; + lexer: Lexer; + tokens: any; + index: number; -Parser.prototype = { - getAst: function() { + constructor(expression: any) { + this.expression = expression; + this.lexer = new Lexer(expression); + this.tokens = this.lexer.tokenize(); + this.index = 0; + } + + getAst() { return this.start(); - }, + } - start: function() { + start() { try { return this.functionCall() || this.metricExpression(); } catch (e) { @@ -22,9 +27,9 @@ Parser.prototype = { pos: e.pos, }; } - }, + } - curlyBraceSegment: function() { + curlyBraceSegment() { if (this.match('identifier', '{') || this.match('{')) { let curlySegment = ''; @@ -51,9 +56,9 @@ Parser.prototype = { } else { return null; } - }, + } - metricSegment: function() { + metricSegment() { const curly = this.curlyBraceSegment(); if (curly) { return curly; @@ -97,14 +102,14 @@ Parser.prototype = { this.consumeToken(); return node; - }, + } - metricExpression: function() { + metricExpression() { if (!this.match('templateStart') && !this.match('identifier') && !this.match('number') && !this.match('{')) { return null; } - const node = { + const node: any = { type: 'metric', segments: [], }; @@ -123,9 +128,9 @@ Parser.prototype = { } return node; - }, + } - functionCall: function() { + functionCall() { if (!this.match('identifier', '(')) { return null; } @@ -147,9 +152,9 @@ Parser.prototype = { this.consumeToken(); return node; - }, + } - boolExpression: function() { + boolExpression() { if (!this.match('bool')) { return null; } @@ -158,9 +163,9 @@ Parser.prototype = { type: 'bool', value: this.consumeToken().value === 'true', }; - }, + } - functionParameters: function() { + functionParameters(): any { if (this.match(')') || this.match('')) { return []; } @@ -179,9 +184,9 @@ Parser.prototype = { this.consumeToken(); return [param].concat(this.functionParameters()); - }, + } - seriesRefExpression: function() { + seriesRefExpression() { if (!this.match('identifier')) { return null; } @@ -197,9 +202,9 @@ Parser.prototype = { type: 'series-ref', value: token.value, }; - }, + } - numericLiteral: function() { + numericLiteral() { if (!this.match('number')) { return null; } @@ -208,9 +213,9 @@ Parser.prototype = { type: 'number', value: parseFloat(this.consumeToken().value), }; - }, + } - stringLiteral: function() { + stringLiteral() { if (!this.match('string')) { return null; } @@ -224,29 +229,29 @@ Parser.prototype = { type: 'string', value: token.value, }; - }, + } - errorMark: function(text) { + errorMark(text: string) { const currentToken = this.tokens[this.index]; const 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() { + consumeToken() { this.index++; return this.tokens[this.index - 1]; - }, + } - matchToken: function(type, index) { + matchToken(type: any, index: number) { const token = this.tokens[this.index + index]; return (token === undefined && type === '') || (token && token.type === type); - }, + } - match: function(token1, token2) { + match(token1: any, token2?: any) { return this.matchToken(token1, 0) && (!token2 || this.matchToken(token2, 1)); - }, -}; + } +}