|
|
@@ -20,186 +20,147 @@ define([
|
|
|
this.lexer = new Lexer(expression);
|
|
|
this.state = "start";
|
|
|
this.error = null;
|
|
|
+ this.tokens = this.lexer.tokenize();
|
|
|
+ this.index = 0;
|
|
|
}
|
|
|
|
|
|
Parser.Nodes = NodeTypes;
|
|
|
|
|
|
Parser.prototype = {
|
|
|
- getAst: function () {
|
|
|
- return this.parse('start');
|
|
|
- },
|
|
|
|
|
|
- isUnexpectedToken: function (expected, value) {
|
|
|
- if (this.token === null) {
|
|
|
- this.error = "Expected token: " + expected + " instead found end of string";
|
|
|
- return true;
|
|
|
- }
|
|
|
+ getAst: function () {
|
|
|
+ return this.start();
|
|
|
+ },
|
|
|
|
|
|
- if (this.token.type === expected) {
|
|
|
- return false;
|
|
|
- }
|
|
|
+ start: function () {
|
|
|
+ return this.functionCall() || this.metricExpression();
|
|
|
+ },
|
|
|
|
|
|
- if (value && this.token.value === value) {
|
|
|
- return false;
|
|
|
- }
|
|
|
+ metricExpression: function() {
|
|
|
+ if (!this.match('identifier')) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ var node = {
|
|
|
+ type: 'metric',
|
|
|
+ segments: [{
|
|
|
+ type: 'segment',
|
|
|
+ value: this.tokens[this.index].value
|
|
|
+ }]
|
|
|
+ }
|
|
|
+
|
|
|
+ this.index++;
|
|
|
+
|
|
|
+ if (this.match('.')) {
|
|
|
+ this.index++;
|
|
|
+ var rest = this.metricExpression();
|
|
|
+ node.segments = node.segments.concat(rest.segments)
|
|
|
+ }
|
|
|
+
|
|
|
+ return node;
|
|
|
+ },
|
|
|
+
|
|
|
+ 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))
|
|
|
+ },
|
|
|
+
|
|
|
+ functionCall: function() {
|
|
|
+ if (!this.match('identifier', '(')) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ var node = {
|
|
|
+ type: 'function',
|
|
|
+ name: this.tokens[this.index].value,
|
|
|
+ };
|
|
|
+
|
|
|
+ this.index += 2;
|
|
|
+
|
|
|
+ node.params = this.functionParameters();
|
|
|
+
|
|
|
+ if (!this.match(')')) {
|
|
|
+ this.error = 'missing closing paranthesis';
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.index++;
|
|
|
+
|
|
|
+ return node;
|
|
|
+ },
|
|
|
|
|
|
- this.error = "Expected token " + expected +
|
|
|
- ' instead found token ' + this.token.type +
|
|
|
- ' ("' + this.token.value + '")' +
|
|
|
- " at position: " + this.lexer.char;
|
|
|
+ functionParameters: function () {
|
|
|
+ if (this.match(')') || this.match('')) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ var param =
|
|
|
+ this.functionCall() ||
|
|
|
+ this.metricExpression() ||
|
|
|
+ this.numericLiteral() ||
|
|
|
+ this.stringLiteral();
|
|
|
+
|
|
|
+ if (!this.match(',')) {
|
|
|
+ return [param];
|
|
|
+ }
|
|
|
+
|
|
|
+ this.index++;
|
|
|
+ return [param].concat(this.functionParameters());
|
|
|
+ },
|
|
|
+
|
|
|
+ numericLiteral: function () {
|
|
|
+ if (!this.match('number')) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.index++;
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'number',
|
|
|
+ value: this.tokens[this.index-1].value
|
|
|
+ };
|
|
|
+ },
|
|
|
|
|
|
+ stringLiteral: function () {
|
|
|
+ if (!this.match('string')) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.index++;
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'string',
|
|
|
+ value: this.tokens[this.index-1].value
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ isUnexpectedToken: function (expected, value) {
|
|
|
+ if (this.token === null) {
|
|
|
+ this.error = "Expected token: " + expected + " instead found end of string";
|
|
|
return true;
|
|
|
- },
|
|
|
-
|
|
|
- parse: function (state, allowParams) {
|
|
|
- var node = { };
|
|
|
-
|
|
|
- while(true) {
|
|
|
- this.token = this.lexer.next();
|
|
|
-
|
|
|
- switch(state) {
|
|
|
- case "start":
|
|
|
- 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";
|
|
|
- this.prevToken = this.token;
|
|
|
- break;
|
|
|
-
|
|
|
- case "identifier":
|
|
|
- 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 (this.token.value === ')') {
|
|
|
- return node;
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
-
|
|
|
- case 'metricEnd':
|
|
|
- if (this.token === null) {
|
|
|
- return node;
|
|
|
- }
|
|
|
-
|
|
|
- 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 (this.isUnexpectedToken(Lexer.Token.Identifier)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- node.segments.push({
|
|
|
- type: NodeTypes.MetricNode,
|
|
|
- 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;
|
|
|
- }
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
+ if (this.token.type === expected) {
|
|
|
+ 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;
|
|
|
+ },
|
|
|
};
|
|
|
|
|
|
return Parser;
|