improved parser code

This commit is contained in:
Torkel Ödegaard
2013-12-24 09:32:51 +01:00
parent 35ae9e3379
commit 2ded42e9fe
4 changed files with 159 additions and 310 deletions
+132 -171
View File
@@ -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;
}
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, 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;
}
}
start: function () {
return this.functionCall() || this.metricExpression();
},
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;
},
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;
}
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;