angular.js/src/parser.js

775 lines
20 KiB
JavaScript
Raw Normal View History

'use strict';
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;},
'=':noop,
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, parseStringsForObjects){
var dateParseLength = parseStringsForObjects ? DATE_ISOSTRING_LN : -1,
tokens = [],
token,
index = 0,
json = [],
ch,
lastCh = ':'; // can start regexp
while (index < text.length) {
ch = text.charAt(index);
if (is('"\'')) {
readString(ch);
} else if (isNumber(ch) || is('.') && isNumber(peek())) {
readNumber();
} else if (isIdent(ch)) {
readIdent();
2010-12-11 18:07:10 +00:00
// identifiers can only be if the preceding char was a { or ,
if (was('{,') && json[0]=='{' &&
(token=tokens[tokens.length-1])) {
token.json = token.text.indexOf('.') == -1;
}
} else if (is('(){}[].,;:')) {
2010-12-11 18:07:10 +00:00
tokens.push({
index:index,
text:ch,
2010-12-11 18:07:10 +00:00
json:(was(':[,') && is('{[')) || is('}]:,')
});
if (is('{[')) json.unshift(ch);
if (is('}]')) json.shift();
index++;
} else if (isWhitespace(ch)) {
index++;
continue;
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) {
2010-10-20 14:22:15 +00:00
tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
index += 1;
2010-01-12 01:32:33 +00:00
} else {
throwError("Unexpected next character ", index, index+1);
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
}
lastCh = ch;
}
return tokens;
2010-03-24 17:35:01 +00:00
function is(chars) {
return chars.indexOf(ch) != -1;
}
function was(chars) {
return chars.indexOf(lastCh) != -1;
}
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' ||
2010-10-21 06:17:59 +00:00
ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
}
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 == '+' || isNumber(ch);
}
function throwError(error, start, end) {
end = end || index;
throw Error("Lexer Error: " + error + " at column" +
(isDefined(start)
? "s " + start + "-" + index + " [" + text.substring(start, end) + "]"
: " " + end) +
" in expression [" + text + "].");
}
function readNumber() {
var number = "";
var start = index;
while (index < text.length) {
var ch = lowercase(text.charAt(index));
if (ch == '.' || isNumber(ch)) {
number += ch;
} 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') {
throwError('Invalid exponent');
} else {
break;
}
}
index++;
}
number = 1 * number;
tokens.push({index:start, text:number, json:true,
fn:function(){return number;}});
}
function readIdent() {
var ident = "";
var start = index;
var fn;
while (index < text.length) {
var ch = text.charAt(index);
if (ch == '.' || isIdent(ch) || isNumber(ch)) {
ident += ch;
} else {
break;
2010-01-12 01:32:33 +00:00
}
index++;
}
fn = OPERATORS[ident];
tokens.push({
index:start,
text:ident,
json: fn,
fn:fn||extend(getterFn(ident), {
assign:function(self, value){
return setter(self, ident, value);
}
})
});
}
function readString(quote) {
var start = index;
index++;
var string = "";
var rawString = quote;
var escape = false;
while (index < text.length) {
var ch = text.charAt(index);
rawString += ch;
if (escape) {
if (ch == 'u') {
var hex = text.substring(index + 1, index + 5);
if (!hex.match(/[\da-f]{4}/i))
throwError( "Invalid unicode escape [\\u" + hex + "]");
index += 4;
string += String.fromCharCode(parseInt(hex, 16));
} else {
var rep = ESCAPE[ch];
if (rep) {
string += rep;
} else {
string += ch;
}
}
escape = false;
} else if (ch == '\\') {
escape = true;
} else if (ch == quote) {
index++;
tokens.push({index:start, text:rawString, string:string, json:true,
fn:function(){
return (string.length == dateParseLength)
? angular['String']['toDate'](string)
: string;
}});
return;
} else {
string += ch;
}
index++;
}
throwError("Unterminated quote", start);
}
}
2010-01-06 00:36:58 +00:00
2010-01-12 01:32:33 +00:00
/////////////////////////////////////////
2010-01-06 00:36:58 +00:00
function parser(text, json){
var ZERO = valueFn(0),
tokens = lex(text, json),
assignment = _assignment,
assignable = logicalOR,
functionCall = _functionCall,
fieldAccess = _fieldAccess,
objectIndex = _objectIndex,
filterChain = _filterChain,
functionIdent = _functionIdent,
pipeFunction = _pipeFunction;
if(json){
// The extra level of aliasing is here, just in case the lexer misses something, so that
// we prevent any accidental execution in JSON.
assignment = logicalOR;
functionCall =
fieldAccess =
objectIndex =
assignable =
filterChain =
functionIdent =
pipeFunction =
function (){ throwError("is not valid json", {text:text, index:0}); };
}
2011-04-19 23:34:49 +00:00
//TODO: Shouldn't all of the public methods have assertAllConsumed?
//TODO: I think these should be public as part of the parser api instead of scope.$eval().
return {
assignable: assertConsumed(assignable),
primary: assertConsumed(primary),
statements: assertConsumed(statements),
validator: assertConsumed(validator),
formatter: assertConsumed(formatter),
filter: assertConsumed(filter)
};
function assertConsumed(fn) {
return function(){
var value = fn();
if (tokens.length !== 0) {
throwError("is an unexpected token", tokens[0]);
}
return value;
};
}
///////////////////////////////////
function throwError(msg, token) {
throw Error("Syntax Error: Token '" + token.text +
"' " + msg + " at column " +
(token.index + 1) + " of the expression [" +
text + "] starting at [" + text.substring(token.index) + "].");
}
2010-03-24 17:35:01 +00:00
function peekToken() {
if (tokens.length === 0)
throw Error("Unexpected end of expression: " + text);
return tokens[0];
}
2010-03-24 17:35:01 +00:00
function peek(e1, e2, e3, e4) {
2010-01-12 01:32:33 +00:00
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
function expect(e1, e2, e3, e4){
var token = peek(e1, e2, e3, e4);
2010-01-12 01:32:33 +00:00
if (token) {
if (json && !token.json) {
throwError("is not valid json", token);
}
tokens.shift();
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
function consume(e1){
if (!expect(e1)) {
throwError("is unexpected, expecting [" + e1 + "]", peek());
2010-01-06 00:36:58 +00:00
}
}
2010-03-24 17:35:01 +00:00
function unaryFn(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
function binaryFn(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
function hasTokens () {
return tokens.length > 0;
}
2010-03-24 17:35:01 +00:00
function statements(){
2010-01-12 01:32:33 +00:00
var statements = [];
while(true) {
if (tokens.length > 0 && !peek('}', ')', ';', ']'))
statements.push(filterChain());
if (!expect(';')) {
// optimize for the common case where there is only one statement.
// TODO(size): maybe we should not support multiple statements?
return statements.length == 1
? statements[0]
: 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-12 01:32:33 +00:00
}
2010-01-06 00:36:58 +00:00
}
}
2010-03-24 17:35:01 +00:00
function _filterChain(){
var left = expression();
2010-01-12 01:32:33 +00:00
var token;
while(true) {
if ((token = expect('|'))) {
left = binaryFn(left, token.fn, filter());
2010-01-12 01:32:33 +00:00
} else {
return left;
}
}
}
2010-03-24 17:35:01 +00:00
function filter(){
return pipeFunction(angularFilter);
}
2010-03-24 17:35:01 +00:00
function validator(){
return pipeFunction(angularValidator);
}
2010-03-24 17:35:01 +00:00
function formatter(){
var token = expect();
var formatter = angularFormatter[token.text];
var argFns = [];
if (!formatter) throwError('is not a valid formatter.', token);
while(true) {
if ((token = expect(':'))) {
argFns.push(expression());
} else {
return valueFn({
format:invokeFn(formatter.format),
parse:invokeFn(formatter.parse)
});
}
}
function invokeFn(fn){
return function(self, input){
var args = [input];
for ( var i = 0; i < argFns.length; i++) {
args.push(argFns[i](self));
}
return fn.apply(self, args);
};
}
}
function _pipeFunction(fnScope){
var fn = functionIdent(fnScope);
2010-01-12 01:32:33 +00:00
var argsFn = [];
var token;
while(true) {
if ((token = expect(':'))) {
argsFn.push(expression());
2010-01-12 01:32:33 +00:00
} 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
function expression(){
return assignment();
}
2010-03-24 17:35:01 +00:00
function _assignment(){
var left = logicalOR();
var right;
2010-01-12 01:32:33 +00:00
var token;
if (token = expect('=')) {
if (!left.assign) {
throwError("implies assignment but [" +
text.substring(0, token.index) + "] can not be assigned to", token);
2010-01-12 01:32:33 +00:00
}
right = logicalOR();
return function(self){
return left.assign(self, right(self));
};
2010-01-06 00:36:58 +00:00
} else {
return left;
2010-01-06 00:36:58 +00:00
}
}
2010-03-24 17:35:01 +00:00
function logicalOR(){
var left = logicalAND();
2010-01-12 01:32:33 +00:00
var token;
while(true) {
if ((token = expect('||'))) {
left = binaryFn(left, token.fn, logicalAND());
2010-01-12 01:32:33 +00:00
} else {
return left;
}
}
}
2010-03-24 17:35:01 +00:00
function logicalAND(){
var left = equality();
2010-01-12 01:32:33 +00:00
var token;
if ((token = expect('&&'))) {
left = binaryFn(left, token.fn, logicalAND());
2010-01-06 00:36:58 +00:00
}
return left;
}
2010-03-24 17:35:01 +00:00
function equality(){
var left = relational();
2010-01-12 01:32:33 +00:00
var token;
if ((token = expect('==','!='))) {
left = binaryFn(left, token.fn, equality());
2010-01-12 01:32:33 +00:00
}
return left;
}
2010-03-24 17:35:01 +00:00
function relational(){
var left = additive();
2010-01-12 01:32:33 +00:00
var token;
if (token = expect('<', '>', '<=', '>=')) {
left = binaryFn(left, token.fn, relational());
2010-01-12 01:32:33 +00:00
}
return left;
}
2010-03-24 17:35:01 +00:00
function additive(){
var left = multiplicative();
2010-01-12 01:32:33 +00:00
var token;
while(token = expect('+','-')) {
left = binaryFn(left, token.fn, multiplicative());
2010-01-12 01:32:33 +00:00
}
return left;
}
2010-03-24 17:35:01 +00:00
function multiplicative(){
var left = unary();
2010-01-12 01:32:33 +00:00
var token;
while(token = expect('*','/','%')) {
left = binaryFn(left, token.fn, unary());
2010-01-12 01:32:33 +00:00
}
return left;
}
2010-03-24 17:35:01 +00:00
function unary(){
2010-01-12 01:32:33 +00:00
var token;
if (expect('+')) {
return primary();
} else if (token = expect('-')) {
return binaryFn(ZERO, token.fn, unary());
} else if (token = expect('!')) {
return unaryFn(token.fn, unary());
2010-01-06 00:36:58 +00:00
} else {
return primary();
2010-01-06 00:36:58 +00:00
}
}
2010-03-24 17:35:01 +00:00
function _functionIdent(fnScope) {
var token = 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) {
throwError("should be a function", token);
2010-01-12 01:32:33 +00:00
}
return instance;
}
2010-03-24 17:35:01 +00:00
function primary() {
2010-01-12 01:32:33 +00:00
var primary;
if (expect('(')) {
var expression = filterChain();
consume(')');
2010-01-12 01:32:33 +00:00
primary = expression;
} else if (expect('[')) {
primary = arrayDeclaration();
} else if (expect('{')) {
primary = object();
2010-01-06 00:36:58 +00:00
} else {
var token = expect();
2010-01-12 01:32:33 +00:00
primary = token.fn;
if (!primary) {
throwError("not a primary expression", token);
2010-01-12 01:32:33 +00:00
}
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
var next;
while (next = expect('(', '[', '.')) {
2010-01-12 01:32:33 +00:00
if (next.text === '(') {
primary = functionCall(primary);
2010-01-12 01:32:33 +00:00
} else if (next.text === '[') {
primary = objectIndex(primary);
2010-01-12 01:32:33 +00:00
} else if (next.text === '.') {
primary = fieldAccess(primary);
2010-01-12 01:32:33 +00:00
} else {
throwError("IMPOSSIBLE");
2010-01-12 01:32:33 +00:00
}
}
return primary;
}
2010-03-24 17:35:01 +00:00
function _fieldAccess(object) {
var field = expect().text;
var getter = getterFn(field);
return extend(function (self){
return getter(object(self));
}, {
assign:function(self, value){
return setter(object(self), field, value);
}
});
}
2010-03-24 17:35:01 +00:00
function _objectIndex(obj) {
var indexFn = expression();
consume(']');
return extend(
function (self){
2010-01-12 01:32:33 +00:00
var o = obj(self);
var i = indexFn(self);
return (o) ? o[i] : undefined;
}, {
assign:function(self, value){
return obj(self)[indexFn(self)] = value;
}
});
}
2010-03-24 17:35:01 +00:00
function _functionCall(fn) {
2010-01-12 01:32:33 +00:00
var argsFn = [];
if (peekToken().text != ')') {
2010-01-12 01:32:33 +00:00
do {
argsFn.push(expression());
} while (expect(','));
2010-01-12 01:32:33 +00:00
}
consume(')');
2010-01-12 01:32:33 +00:00
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-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
// This is used with json array declaration
function arrayDeclaration () {
2010-01-12 01:32:33 +00:00
var elementFns = [];
if (peekToken().text != ']') {
2010-01-12 01:32:33 +00:00
do {
elementFns.push(expression());
} while (expect(','));
2010-01-12 01:32:33 +00:00
}
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-03-24 17:35:01 +00:00
function object () {
2010-01-12 01:32:33 +00:00
var keyValues = [];
if (peekToken().text != '}') {
2010-01-12 01:32:33 +00:00
do {
var token = expect(),
key = token.string || token.text;
consume(":");
var value = expression();
2010-01-12 01:32:33 +00:00
keyValues.push({key:key, value:value});
} while (expect(','));
2010-01-12 01:32:33 +00:00
}
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-03-24 17:35:01 +00:00
function watchDecl () {
var anchorName = expect().text;
consume(":");
var expressionFn;
if (peekToken().text == '{') {
consume("{");
expressionFn = statements();
consume("}");
2010-01-06 00:36:58 +00:00
} else {
expressionFn = expression();
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
return function(self) {
return {name:anchorName, fn:expressionFn};
2010-01-12 01:32:33 +00:00
};
2010-01-06 00:36:58 +00:00
}
}
//////////////////////////////////////////////////
// Parser helper functions
//////////////////////////////////////////////////
function setter(obj, path, setValue) {
var element = path.split('.');
for (var i = 0; element.length > 1; i++) {
var key = element.shift();
var propertyObj = obj[key];
if (!propertyObj) {
propertyObj = {};
obj[key] = propertyObj;
}
obj = propertyObj;
}
obj[element.shift()] = setValue;
return setValue;
}
/**
* Return the value accesible from the object by path. Any undefined traversals are ignored
* @param {Object} obj starting object
* @param {string} path path to traverse
* @param {boolean=true} bindFnToScope
* @returns value as accesbile by path
*/
function getter(obj, path, bindFnToScope) {
if (!path) return obj;
var keys = path.split('.');
var key;
var lastInstance = obj;
var len = keys.length;
for (var i = 0; i < len; i++) {
key = keys[i];
if (obj) {
obj = (lastInstance = obj)[key];
}
if (isUndefined(obj) && key.charAt(0) == '$') {
var type = angularGlobal.typeOf(lastInstance);
type = angular[type.charAt(0).toUpperCase()+type.substring(1)];
var fn = type ? type[[key.substring(1)]] : _undefined;
if (fn) {
return obj = bind(lastInstance, fn, lastInstance);
}
}
}
if (!bindFnToScope && isFunction(obj)) {
return bind(lastInstance, obj);
}
return obj;
}
var getterFnCache = {},
compileCache = {},
JS_KEYWORDS = {};
forEach(
("abstract,boolean,break,byte,case,catch,char,class,const,continue,debugger,default," +
"delete,do,double,else,enum,export,extends,false,final,finally,float,for,function,goto," +
"if,implements,import,ininstanceof,intinterface,long,native,new,null,package,private," +
"protected,public,return,short,static,super,switch,synchronized,this,throw,throws," +
"transient,true,try,typeof,var,volatile,void,undefined,while,with").split(/,/),
function(key){ JS_KEYWORDS[key] = true;}
);
function getterFn(path) {
var fn = getterFnCache[path];
if (fn) return fn;
var code = 'var l, fn, t;\n';
forEach(path.split('.'), function(key) {
key = (JS_KEYWORDS[key]) ? '["' + key + '"]' : '.' + key;
code += 'if(!s) return s;\n' +
'l=s;\n' +
's=s' + key + ';\n' +
'if(typeof s=="function" && !(s instanceof RegExp)) s = function(){ return l' +
key + '.apply(l, arguments); };\n';
if (key.charAt(1) == '$') {
// special code for super-imposed functions
var name = key.substr(2);
code += 'if(!s) {\n' +
' t = angular.Global.typeOf(l);\n' +
' fn = (angular[t.charAt(0).toUpperCase() + t.substring(1)]||{})["' + name + '"];\n' +
' if (fn) s = function(){ return fn.apply(l, ' +
'[l].concat(Array.prototype.slice.call(arguments, 0))); };\n' +
'}\n';
}
});
code += 'return s;';
fn = Function('s', code);
fn["toString"] = function(){ return code; };
return getterFnCache[path] = fn;
}
///////////////////////////////////
// TODO(misko): Should this function be public?
function compileExpr(expr) {
return parser(expr).statements();
}
// TODO(misko): Deprecate? Remove!
// I think that compilation should be a service.
function expressionCompile(exp) {
if (typeof exp === $function) return exp;
var fn = compileCache[exp];
if (!fn) {
fn = compileCache[exp] = parser(exp).statements();
}
return fn;
}