angular.js/src/Parser.js

658 lines
17 KiB
JavaScript
Raw Normal View History

var OPERATORS = {
'null':function(self){return _null;},
2010-01-06 00:36:58 +00:00
'true':function(self){return true;},
'false':function(self){return false;},
$undefined:noop,
2010-04-01 00:56:16 +00:00
'+':function(self, a,b){return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
'-':function(self, a,b){return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
2010-01-06 00:36:58 +00:00
'*':function(self, a,b){return a*b;},
'/':function(self, a,b){return a/b;},
'%':function(self, a,b){return a%b;},
'^':function(self, a,b){return a^b;},
'=':function(self, a,b){return setter(self, a, b);},
2010-01-06 00:36:58 +00:00
'==':function(self, a,b){return a==b;},
'!=':function(self, a,b){return a!=b;},
'<':function(self, a,b){return a<b;},
'>':function(self, a,b){return a>b;},
'<=':function(self, a,b){return a<=b;},
'>=':function(self, a,b){return a>=b;},
'&&':function(self, a,b){return a&&b;},
'||':function(self, a,b){return a||b;},
'&':function(self, a,b){return a&b;},
// '|':function(self, a,b){return a|b;},
'|':function(self, a,b){return b(self, a);},
'!':function(self, a){return !a;}
};
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
2010-01-06 00:36:58 +00:00
function lex(text, parseStrings){
var dateParseLength = parseStrings ? 20 : -1,
tokens = [],
index = 0,
canStartRegExp = true;
while (index < text.length) {
var ch = text.charAt(index);
if (ch == '"' || ch == "'") {
readString(ch);
canStartRegExp = true;
} else if (ch == '(' || ch == '[') {
tokens.push({index:index, text:ch});
index++;
} else if (ch == '{' ) {
var peekCh = peek();
if (peekCh == ':' || peekCh == '(') {
tokens.push({index:index, text:ch + peekCh});
index++;
} else {
tokens.push({index:index, text:ch});
}
index++;
canStartRegExp = true;
} else if (ch == ')' || ch == ']' || ch == '}' ) {
tokens.push({index:index, text:ch});
index++;
canStartRegExp = false;
} else if (ch == '.' && isNumber(peek())) {
readNumber();
canStartRegExp = false;
} else if ( ch == ':' || ch == '.' || ch == ',' || ch == ';') {
tokens.push({index:index, text:ch});
index++;
canStartRegExp = true;
} else if ( canStartRegExp && ch == '/' ) {
readRegexp();
canStartRegExp = false;
} else if ( isNumber(ch) ) {
readNumber();
canStartRegExp = false;
} else if (isIdent(ch)) {
readIdent();
canStartRegExp = false;
} else if (isWhitespace(ch)) {
index++;
2010-01-12 01:32:33 +00:00
} else {
var ch2 = ch + peek(),
fn = OPERATORS[ch],
fn2 = OPERATORS[ch2];
if (fn2) {
tokens.push({index:index, text:ch2, fn:fn2});
index += 2;
} else if (fn) {
tokens.push({index:index, text:ch, fn:fn});
index += 1;
2010-01-12 01:32:33 +00:00
} else {
throw "Lexer Error: Unexpected next character [" +
text.substring(index) +
"] in expression '" + text +
"' at column '" + (index+1) + "'.";
2010-01-06 00:36:58 +00:00
}
canStartRegExp = true;
2010-01-12 01:32:33 +00:00
}
}
return tokens;
2010-03-24 17:35:01 +00:00
function peek() {
return index + 1 < text.length ? text.charAt(index + 1) : false;
}
function isNumber(ch) {
2010-01-12 01:32:33 +00:00
return '0' <= ch && ch <= '9';
}
function isWhitespace(ch) {
2010-01-12 01:32:33 +00:00
return ch == ' ' || ch == '\r' || ch == '\t' ||
ch == '\n' || ch == '\v';
}
function isIdent(ch) {
2010-01-12 01:32:33 +00:00
return 'a' <= ch && ch <= 'z' ||
'A' <= ch && ch <= 'Z' ||
'_' == ch || ch == '$';
}
function isExpOperator(ch) {
return ch == '-' || ch == '+';
}
function readNumber() {
2010-01-12 01:32:33 +00:00
var number = "";
var start = index;
while (index < text.length) {
var ch = text.charAt(index);
if (ch == '.' || isNumber(ch)) {
2010-01-12 01:32:33 +00:00
number += ch;
2010-01-06 00:36:58 +00:00
} else {
var peekCh = peek();
if (ch == 'E' && isExpOperator(peekCh)) {
number += ch;
} else if (isExpOperator(ch) &&
peekCh && isNumber(peekCh) &&
number.charAt(number.length - 1) == 'E') {
number += ch;
} else if (isExpOperator(ch) &&
(!peekCh || !isNumber(peekCh)) &&
number.charAt(number.length - 1) == 'E') {
throw 'Lexer found invalid exponential value "' + text + '"';
} else {
break;
}
2010-01-06 00:36:58 +00:00
}
index++;
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
number = 1 * number;
tokens.push({index:start, text:number,
2010-01-12 01:32:33 +00:00
fn:function(){return number;}});
}
function readIdent() {
2010-01-12 01:32:33 +00:00
var ident = "";
var start = index;
while (index < text.length) {
var ch = text.charAt(index);
if (ch == '.' || isIdent(ch) || isNumber(ch)) {
2010-01-12 01:32:33 +00:00
ident += ch;
} else {
break;
}
index++;
2010-01-06 00:36:58 +00:00
}
var fn = OPERATORS[ident];
2010-01-12 01:32:33 +00:00
if (!fn) {
fn = getterFn(ident);
2010-01-12 01:32:33 +00:00
fn.isAssignable = ident;
2010-01-06 00:36:58 +00:00
}
tokens.push({index:start, text:ident, fn:fn});
}
function readString(quote) {
var start = index;
index++;
2010-01-12 01:32:33 +00:00
var string = "";
2010-03-24 17:35:01 +00:00
var rawString = quote;
2010-01-12 01:32:33 +00:00
var escape = false;
while (index < text.length) {
var ch = text.charAt(index);
2010-03-24 17:35:01 +00:00
rawString += ch;
2010-01-12 01:32:33 +00:00
if (escape) {
if (ch == 'u') {
var hex = text.substring(index + 1, index + 5);
2010-10-15 21:06:30 +00:00
if (!hex.match(/[\da-f]{4}/i))
throw "Lexer Error: Invalid unicode escape [\\u" +
hex + "] starting at column '" +
start + "' in expression '" + text + "'.";
index += 4;
2010-01-12 01:32:33 +00:00
string += String.fromCharCode(parseInt(hex, 16));
2010-01-06 00:36:58 +00:00
} else {
var rep = ESCAPE[ch];
2010-01-12 01:32:33 +00:00
if (rep) {
string += rep;
} else {
string += ch;
}
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
escape = false;
} else if (ch == '\\') {
escape = true;
} else if (ch == quote) {
index++;
tokens.push({index:start, text:rawString, string:string,
2010-01-12 01:32:33 +00:00
fn:function(){
return (string.length == dateParseLength) ?
angular['String']['toDate'](string) : string;
}});
return;
} else {
string += ch;
2010-01-06 00:36:58 +00:00
}
index++;
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
throw "Lexer Error: Unterminated quote [" +
text.substring(start) + "] starting at column '" +
(start+1) + "' in expression '" + text + "'.";
}
function readRegexp(quote) {
var start = index;
index++;
2010-01-12 01:32:33 +00:00
var regexp = "";
var escape = false;
while (index < text.length) {
var ch = text.charAt(index);
2010-01-12 01:32:33 +00:00
if (escape) {
regexp += ch;
escape = false;
} else if (ch === '\\') {
regexp += ch;
escape = true;
} else if (ch === '/') {
index++;
2010-01-12 01:32:33 +00:00
var flags = "";
if (isIdent(text.charAt(index))) {
readIdent();
flags = tokens.pop().text;
2010-01-12 01:32:33 +00:00
}
var compiledRegexp = new RegExp(regexp, flags);
tokens.push({index:start, text:regexp, flags:flags,
2010-01-12 01:32:33 +00:00
fn:function(){return compiledRegexp;}});
return;
} else {
regexp += ch;
2010-01-06 00:36:58 +00:00
}
index++;
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
throw "Lexer Error: Unterminated RegExp [" +
text.substring(start) + "] starting at column '" +
(start+1) + "' in expression '" + text + "'.";
2010-01-06 00:36:58 +00:00
}
}
2010-01-06 00:36:58 +00:00
2010-01-12 01:32:33 +00:00
/////////////////////////////////////////
2010-01-06 00:36:58 +00:00
2010-01-12 01:32:33 +00:00
function Parser(text, parseStrings){
2010-01-06 00:36:58 +00:00
this.text = text;
this.tokens = lex(text, parseStrings);
2010-01-06 00:36:58 +00:00
this.index = 0;
2010-04-04 00:04:36 +00:00
}
2010-01-06 00:36:58 +00:00
var ZERO = function(){
2010-01-06 00:36:58 +00:00
return 0;
};
2010-01-12 01:32:33 +00:00
Parser.prototype = {
error: function(msg, token) {
2010-03-24 17:35:01 +00:00
throw "Token '" + token.text +
"' is " + msg + " at column='" +
(token.index + 1) + "' of expression '" +
2010-01-12 01:32:33 +00:00
this.text + "' starting at '" + this.text.substring(token.index) + "'.";
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
peekToken: function() {
2010-03-24 17:35:01 +00:00
if (this.tokens.length === 0)
2010-01-12 01:32:33 +00:00
throw "Unexpected end of expression: " + this.text;
return this.tokens[0];
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
peek: function(e1, e2, e3, e4) {
var tokens = this.tokens;
if (tokens.length > 0) {
var token = tokens[0];
var t = token.text;
if (t==e1 || t==e2 || t==e3 || t==e4 ||
(!e1 && !e2 && !e3 && !e4)) {
return token;
}
}
return false;
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
expect: function(e1, e2, e3, e4){
var token = this.peek(e1, e2, e3, e4);
if (token) {
this.tokens.shift();
this.currentToken = token;
2010-01-06 00:36:58 +00:00
return token;
}
2010-01-12 01:32:33 +00:00
return false;
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
consume: function(e1){
if (!this.expect(e1)) {
var token = this.peek();
throw "Expecting '" + e1 + "' at column '" +
(token.index+1) + "' in '" +
this.text + "' got '" +
this.text.substring(token.index) + "'.";
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
_unary: function(fn, right) {
2010-01-12 01:32:33 +00:00
return function(self) {
return fn(self, right(self));
};
},
2010-03-24 17:35:01 +00:00
_binary: function(left, fn, right) {
2010-01-12 01:32:33 +00:00
return function(self) {
return fn(self, left(self), right(self));
};
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
hasTokens: function () {
return this.tokens.length > 0;
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
assertAllConsumed: function(){
if (this.tokens.length !== 0) {
throw "Did not understand '" + this.text.substring(this.tokens[0].index) +
"' while evaluating '" + this.text + "'.";
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
statements: function(){
var statements = [];
while(true) {
if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
statements.push(this.filterChain());
if (!this.expect(';')) {
return function (self){
var value;
for ( var i = 0; i < statements.length; i++) {
var statement = statements[i];
if (statement)
value = statement(self);
}
return value;
};
}
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
filterChain: function(){
var left = this.expression();
var token;
while(true) {
if ((token = this.expect('|'))) {
left = this._binary(left, token.fn, this.filter());
2010-01-12 01:32:33 +00:00
} else {
return left;
}
}
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
filter: function(){
2010-02-13 03:39:01 +00:00
return this._pipeFunction(angularFilter);
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
validator: function(){
2010-02-13 03:39:01 +00:00
return this._pipeFunction(angularValidator);
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
_pipeFunction: function(fnScope){
var fn = this.functionIdent(fnScope);
var argsFn = [];
var token;
while(true) {
if ((token = this.expect(':'))) {
argsFn.push(this.expression());
} else {
var fnInvoke = function(self, input){
var args = [input];
for ( var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self));
}
return fn.apply(self, args);
2010-01-12 01:32:33 +00:00
};
return function(){
return fnInvoke;
};
}
}
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
expression: function(){
return this.throwStmt();
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
throwStmt: function(){
if (this.expect('throw')) {
var throwExp = this.assignment();
return function (self) {
throw throwExp(self);
};
2010-01-06 00:36:58 +00:00
} else {
2010-01-12 01:32:33 +00:00
return this.assignment();
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
assignment: function(){
var left = this.logicalOR();
var token;
if (token = this.expect('=')) {
if (!left.isAssignable) {
throw "Left hand side '" +
this.text.substring(0, token.index) + "' of assignment '" +
this.text.substring(token.index) + "' is not assignable.";
}
var ident = function(){return left.isAssignable;};
return this._binary(ident, token.fn, this.logicalOR());
2010-01-06 00:36:58 +00:00
} else {
2010-01-12 01:32:33 +00:00
return left;
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
logicalOR: function(){
var left = this.logicalAND();
var token;
while(true) {
if ((token = this.expect('||'))) {
left = this._binary(left, token.fn, this.logicalAND());
2010-01-12 01:32:33 +00:00
} else {
return left;
}
}
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
logicalAND: function(){
var left = this.equality();
2010-01-12 01:32:33 +00:00
var token;
if ((token = this.expect('&&'))) {
left = this._binary(left, token.fn, this.logicalAND());
2010-01-06 00:36:58 +00:00
}
return left;
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
equality: function(){
var left = this.relational();
var token;
if ((token = this.expect('==','!='))) {
left = this._binary(left, token.fn, this.equality());
2010-01-12 01:32:33 +00:00
}
return left;
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
relational: function(){
var left = this.additive();
var token;
if (token = this.expect('<', '>', '<=', '>=')) {
left = this._binary(left, token.fn, this.relational());
2010-01-12 01:32:33 +00:00
}
return left;
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
additive: function(){
var left = this.multiplicative();
var token;
while(token = this.expect('+','-')) {
left = this._binary(left, token.fn, this.multiplicative());
2010-01-12 01:32:33 +00:00
}
return left;
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
multiplicative: function(){
var left = this.unary();
var token;
while(token = this.expect('*','/','%')) {
left = this._binary(left, token.fn, this.unary());
2010-01-12 01:32:33 +00:00
}
return left;
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
unary: function(){
var token;
if (this.expect('+')) {
return this.primary();
} else if (token = this.expect('-')) {
2010-08-19 01:15:19 +00:00
return this._binary(ZERO, token.fn, this.unary());
} else if (token = this.expect('!')) {
return this._unary(token.fn, this.unary());
2010-01-06 00:36:58 +00:00
} else {
2010-01-12 01:32:33 +00:00
return this.primary();
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
functionIdent: function(fnScope) {
2010-01-06 00:36:58 +00:00
var token = this.expect();
2010-01-12 01:32:33 +00:00
var element = token.text.split('.');
var instance = fnScope;
var key;
for ( var i = 0; i < element.length; i++) {
key = element[i];
if (instance)
instance = instance[key];
2010-01-06 00:36:58 +00:00
}
if (typeof instance != $function) {
2010-01-12 01:32:33 +00:00
throw "Function '" + token.text + "' at column '" +
(token.index+1) + "' in '" + this.text + "' is not defined.";
}
return instance;
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
primary: function() {
var primary;
if (this.expect('(')) {
var expression = this.filterChain();
this.consume(')');
primary = expression;
} else if (this.expect('[')) {
primary = this.arrayDeclaration();
} else if (this.expect('{')) {
primary = this.object();
2010-01-06 00:36:58 +00:00
} else {
2010-01-12 01:32:33 +00:00
var token = this.expect();
primary = token.fn;
if (!primary) {
this.error("not a primary expression", token);
}
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
var next;
while (next = this.expect('(', '[', '.')) {
if (next.text === '(') {
primary = this.functionCall(primary);
} else if (next.text === '[') {
primary = this.objectIndex(primary);
} else if (next.text === '.') {
primary = this.fieldAccess(primary);
} else {
throw "IMPOSSIBLE";
}
}
return primary;
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
fieldAccess: function(object) {
var field = this.expect().text;
var getter = getterFn(field);
2010-01-12 01:32:33 +00:00
var fn = function (self){
return getter(object(self));
2010-01-12 01:32:33 +00:00
};
fn.isAssignable = field;
return fn;
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
objectIndex: function(obj) {
var indexFn = this.expression();
this.consume(']');
if (this.expect('=')) {
var rhs = this.expression();
return function (self){
return obj(self)[indexFn(self)] = rhs(self);
};
} else {
return function (self){
var o = obj(self);
var i = indexFn(self);
return (o) ? o[i] : _undefined;
2010-01-12 01:32:33 +00:00
};
}
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
functionCall: function(fn) {
var argsFn = [];
if (this.peekToken().text != ')') {
do {
argsFn.push(this.expression());
} while (this.expect(','));
}
this.consume(')');
return function (self){
var args = [];
for ( var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self));
}
var fnPtr = fn(self) || noop;
// IE stupidity!
return fnPtr.apply ?
fnPtr.apply(self, args) :
fnPtr(args[0], args[1], args[2], args[3], args[4]);
2010-01-06 00:36:58 +00:00
};
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
// This is used with json array declaration
arrayDeclaration: function () {
var elementFns = [];
if (this.peekToken().text != ']') {
do {
elementFns.push(this.expression());
} while (this.expect(','));
}
this.consume(']');
2010-01-06 00:36:58 +00:00
return function (self){
2010-01-12 01:32:33 +00:00
var array = [];
for ( var i = 0; i < elementFns.length; i++) {
array.push(elementFns[i](self));
}
return array;
2010-01-06 00:36:58 +00:00
};
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
object: function () {
var keyValues = [];
if (this.peekToken().text != '}') {
do {
2010-03-24 17:35:01 +00:00
var token = this.expect(),
key = token.string || token.text;
2010-01-12 01:32:33 +00:00
this.consume(":");
var value = this.expression();
keyValues.push({key:key, value:value});
} while (this.expect(','));
}
this.consume('}');
2010-01-06 00:36:58 +00:00
return function (self){
2010-01-12 01:32:33 +00:00
var object = {};
for ( var i = 0; i < keyValues.length; i++) {
var keyValue = keyValues[i];
var value = keyValue.value(self);
object[keyValue.key] = value;
}
return object;
2010-01-06 00:36:58 +00:00
};
2010-01-12 01:32:33 +00:00
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
watch: function () {
var decl = [];
while(this.hasTokens()) {
decl.push(this.watchDecl());
if (!this.expect(';')) {
this.assertAllConsumed();
}
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
this.assertAllConsumed();
return function (self){
for ( var i = 0; i < decl.length; i++) {
var d = decl[i](self);
self.addListener(d.name, d.fn);
}
};
},
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
watchDecl: function () {
var anchorName = this.expect().text;
this.consume(":");
var expression;
if (this.peekToken().text == '{') {
this.consume("{");
expression = this.statements();
this.consume("}");
2010-01-06 00:36:58 +00:00
} else {
2010-01-12 01:32:33 +00:00
expression = this.expression();
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
return function(self) {
return {name:anchorName, fn:expression};
};
2010-01-06 00:36:58 +00:00
}
};