mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-05-29 00:22:04 +08:00
rename src/Parser.js to src/parser.js
This commit is contained in:
663
src/parser.js
Normal file
663
src/parser.js
Normal file
@@ -0,0 +1,663 @@
|
||||
var OPERATORS = {
|
||||
'null':function(self){return _null;},
|
||||
'true':function(self){return true;},
|
||||
'false':function(self){return false;},
|
||||
$undefined:noop,
|
||||
'+':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);},
|
||||
'*':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);},
|
||||
'==':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", "'":"'", '"':'"'};
|
||||
|
||||
function lex(text, parseStringsForObjects){
|
||||
var dateParseLength = parseStringsForObjects ? 20 : -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 ( was('({[:,;') && is('/') ) {
|
||||
readRegexp();
|
||||
} else if (isIdent(ch)) {
|
||||
readIdent();
|
||||
if (was('{,') && json[0]=='{' &&
|
||||
(token=tokens[tokens.length-1])) {
|
||||
token.json = token.text.indexOf('.') == -1;
|
||||
}
|
||||
} else if (is('(){}[].,;:')) {
|
||||
tokens.push({index:index, text:ch, json:is('{}[]:,')});
|
||||
if (is('{[')) json.unshift(ch);
|
||||
if (is('}]')) json.shift();
|
||||
index++;
|
||||
} else if (isWhitespace(ch)) {
|
||||
index++;
|
||||
continue;
|
||||
} 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, json: was('[,:') && is('+-')});
|
||||
index += 1;
|
||||
} else {
|
||||
throw "Lexer Error: Unexpected next character [" +
|
||||
text.substring(index) +
|
||||
"] in expression '" + text +
|
||||
"' at column '" + (index+1) + "'.";
|
||||
}
|
||||
}
|
||||
lastCh = ch;
|
||||
}
|
||||
return tokens;
|
||||
|
||||
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) {
|
||||
return '0' <= ch && ch <= '9';
|
||||
}
|
||||
function isWhitespace(ch) {
|
||||
return ch == ' ' || ch == '\r' || ch == '\t' ||
|
||||
ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
|
||||
}
|
||||
function isIdent(ch) {
|
||||
return 'a' <= ch && ch <= 'z' ||
|
||||
'A' <= ch && ch <= 'Z' ||
|
||||
'_' == ch || ch == '$';
|
||||
}
|
||||
function isExpOperator(ch) {
|
||||
return ch == '-' || ch == '+';
|
||||
}
|
||||
function readNumber() {
|
||||
var number = "";
|
||||
var start = index;
|
||||
while (index < text.length) {
|
||||
var ch = 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') {
|
||||
throw 'Lexer found invalid exponential value "' + text + '"';
|
||||
} 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;
|
||||
while (index < text.length) {
|
||||
var ch = text.charAt(index);
|
||||
if (ch == '.' || isIdent(ch) || isNumber(ch)) {
|
||||
ident += ch;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
var fn = OPERATORS[ident];
|
||||
if (!fn) {
|
||||
fn = getterFn(ident);
|
||||
fn.isAssignable = ident;
|
||||
}
|
||||
tokens.push({index:start, text:ident, fn:fn, json: OPERATORS[ident]});
|
||||
}
|
||||
|
||||
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))
|
||||
throw "Lexer Error: Invalid unicode escape [\\u" +
|
||||
hex + "] starting at column '" +
|
||||
start + "' in expression '" + text + "'.";
|
||||
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++;
|
||||
}
|
||||
throw "Lexer Error: Unterminated quote [" +
|
||||
text.substring(start) + "] starting at column '" +
|
||||
(start+1) + "' in expression '" + text + "'.";
|
||||
}
|
||||
function readRegexp(quote) {
|
||||
var start = index;
|
||||
index++;
|
||||
var regexp = "";
|
||||
var escape = false;
|
||||
while (index < text.length) {
|
||||
var ch = text.charAt(index);
|
||||
if (escape) {
|
||||
regexp += ch;
|
||||
escape = false;
|
||||
} else if (ch === '\\') {
|
||||
regexp += ch;
|
||||
escape = true;
|
||||
} else if (ch === '/') {
|
||||
index++;
|
||||
var flags = "";
|
||||
if (isIdent(text.charAt(index))) {
|
||||
readIdent();
|
||||
flags = tokens.pop().text;
|
||||
}
|
||||
var compiledRegexp = new RegExp(regexp, flags);
|
||||
tokens.push({index:start, text:regexp, flags:flags,
|
||||
fn:function(){return compiledRegexp;}});
|
||||
return;
|
||||
} else {
|
||||
regexp += ch;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
throw "Lexer Error: Unterminated RegExp [" +
|
||||
text.substring(start) + "] starting at column '" +
|
||||
(start+1) + "' in expression '" + text + "'.";
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
|
||||
function parser(text, json){
|
||||
var ZERO = valueFn(0),
|
||||
tokens = lex(text, json);
|
||||
return {
|
||||
assertAllConsumed: assertAllConsumed,
|
||||
primary: primary,
|
||||
statements: statements,
|
||||
validator: validator,
|
||||
filter: filter,
|
||||
watch: watch
|
||||
};
|
||||
|
||||
///////////////////////////////////
|
||||
|
||||
function error(msg, token) {
|
||||
throw "Token '" + token.text +
|
||||
"' is " + msg + " at column='" +
|
||||
(token.index + 1) + "' of expression '" +
|
||||
text + "' starting at '" + text.substring(token.index) + "'.";
|
||||
}
|
||||
|
||||
function peekToken() {
|
||||
if (tokens.length === 0)
|
||||
throw "Unexpected end of expression: " + text;
|
||||
return tokens[0];
|
||||
}
|
||||
|
||||
function peek(e1, e2, e3, e4) {
|
||||
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;
|
||||
}
|
||||
|
||||
function expect(e1, e2, e3, e4){
|
||||
var token = peek(e1, e2, e3, e4);
|
||||
if (token) {
|
||||
if (json && !token.json) {
|
||||
index = token.index;
|
||||
throw "Expression at column='" +
|
||||
token.index + "' of expression '" +
|
||||
text + "' starting at '" + text.substring(token.index) +
|
||||
"' is not valid json.";
|
||||
}
|
||||
tokens.shift();
|
||||
this.currentToken = token;
|
||||
return token;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function consume(e1){
|
||||
if (!expect(e1)) {
|
||||
var token = peek();
|
||||
throw "Expecting '" + e1 + "' at column '" +
|
||||
(token.index+1) + "' in '" +
|
||||
text + "' got '" +
|
||||
text.substring(token.index) + "'.";
|
||||
}
|
||||
}
|
||||
|
||||
function unaryFn(fn, right) {
|
||||
return function(self) {
|
||||
return fn(self, right(self));
|
||||
};
|
||||
}
|
||||
|
||||
function binaryFn(left, fn, right) {
|
||||
return function(self) {
|
||||
return fn(self, left(self), right(self));
|
||||
};
|
||||
}
|
||||
|
||||
function hasTokens () {
|
||||
return tokens.length > 0;
|
||||
}
|
||||
|
||||
function assertAllConsumed(){
|
||||
if (tokens.length !== 0) {
|
||||
throw "Did not understand '" + text.substring(tokens[0].index) +
|
||||
"' while evaluating '" + text + "'.";
|
||||
}
|
||||
}
|
||||
|
||||
function statements(){
|
||||
var statements = [];
|
||||
while(true) {
|
||||
if (tokens.length > 0 && !peek('}', ')', ';', ']'))
|
||||
statements.push(filterChain());
|
||||
if (!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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function filterChain(){
|
||||
var left = expression();
|
||||
var token;
|
||||
while(true) {
|
||||
if ((token = expect('|'))) {
|
||||
left = binaryFn(left, token.fn, filter());
|
||||
} else {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function filter(){
|
||||
return pipeFunction(angularFilter);
|
||||
}
|
||||
|
||||
function validator(){
|
||||
return pipeFunction(angularValidator);
|
||||
}
|
||||
|
||||
function pipeFunction(fnScope){
|
||||
var fn = functionIdent(fnScope);
|
||||
var argsFn = [];
|
||||
var token;
|
||||
while(true) {
|
||||
if ((token = expect(':'))) {
|
||||
argsFn.push(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);
|
||||
};
|
||||
return function(){
|
||||
return fnInvoke;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function expression(){
|
||||
return throwStmt();
|
||||
}
|
||||
|
||||
function throwStmt(){
|
||||
if (expect('throw')) {
|
||||
var throwExp = assignment();
|
||||
return function (self) {
|
||||
throw throwExp(self);
|
||||
};
|
||||
} else {
|
||||
return assignment();
|
||||
}
|
||||
}
|
||||
|
||||
function assignment(){
|
||||
var left = logicalOR();
|
||||
var token;
|
||||
if (token = expect('=')) {
|
||||
if (!left.isAssignable) {
|
||||
throw "Left hand side '" +
|
||||
text.substring(0, token.index) + "' of assignment '" +
|
||||
text.substring(token.index) + "' is not assignable.";
|
||||
}
|
||||
var ident = function(){return left.isAssignable;};
|
||||
return binaryFn(ident, token.fn, logicalOR());
|
||||
} else {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
|
||||
function logicalOR(){
|
||||
var left = logicalAND();
|
||||
var token;
|
||||
while(true) {
|
||||
if ((token = expect('||'))) {
|
||||
left = binaryFn(left, token.fn, logicalAND());
|
||||
} else {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function logicalAND(){
|
||||
var left = equality();
|
||||
var token;
|
||||
if ((token = expect('&&'))) {
|
||||
left = binaryFn(left, token.fn, logicalAND());
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function equality(){
|
||||
var left = relational();
|
||||
var token;
|
||||
if ((token = expect('==','!='))) {
|
||||
left = binaryFn(left, token.fn, equality());
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function relational(){
|
||||
var left = additive();
|
||||
var token;
|
||||
if (token = expect('<', '>', '<=', '>=')) {
|
||||
left = binaryFn(left, token.fn, relational());
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function additive(){
|
||||
var left = multiplicative();
|
||||
var token;
|
||||
while(token = expect('+','-')) {
|
||||
left = binaryFn(left, token.fn, multiplicative());
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function multiplicative(){
|
||||
var left = unary();
|
||||
var token;
|
||||
while(token = expect('*','/','%')) {
|
||||
left = binaryFn(left, token.fn, unary());
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function unary(){
|
||||
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());
|
||||
} else {
|
||||
return primary();
|
||||
}
|
||||
}
|
||||
|
||||
function functionIdent(fnScope) {
|
||||
var token = expect();
|
||||
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];
|
||||
}
|
||||
if (typeof instance != $function) {
|
||||
throw "Function '" + token.text + "' at column '" +
|
||||
(token.index+1) + "' in '" + text + "' is not defined.";
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
function primary() {
|
||||
var primary;
|
||||
if (expect('(')) {
|
||||
var expression = filterChain();
|
||||
consume(')');
|
||||
primary = expression;
|
||||
} else if (expect('[')) {
|
||||
primary = arrayDeclaration();
|
||||
} else if (expect('{')) {
|
||||
primary = object();
|
||||
} else {
|
||||
var token = expect();
|
||||
primary = token.fn;
|
||||
if (!primary) {
|
||||
error("not a primary expression", token);
|
||||
}
|
||||
}
|
||||
var next;
|
||||
while (next = expect('(', '[', '.')) {
|
||||
if (next.text === '(') {
|
||||
primary = functionCall(primary);
|
||||
} else if (next.text === '[') {
|
||||
primary = objectIndex(primary);
|
||||
} else if (next.text === '.') {
|
||||
primary = fieldAccess(primary);
|
||||
} else {
|
||||
throw "IMPOSSIBLE";
|
||||
}
|
||||
}
|
||||
return primary;
|
||||
}
|
||||
|
||||
function fieldAccess(object) {
|
||||
var field = expect().text;
|
||||
var getter = getterFn(field);
|
||||
var fn = function (self){
|
||||
return getter(object(self));
|
||||
};
|
||||
fn.isAssignable = field;
|
||||
return fn;
|
||||
}
|
||||
|
||||
function objectIndex(obj) {
|
||||
var indexFn = expression();
|
||||
consume(']');
|
||||
if (expect('=')) {
|
||||
var rhs = 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function functionCall(fn) {
|
||||
var argsFn = [];
|
||||
if (peekToken().text != ')') {
|
||||
do {
|
||||
argsFn.push(expression());
|
||||
} while (expect(','));
|
||||
}
|
||||
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]);
|
||||
};
|
||||
}
|
||||
|
||||
// This is used with json array declaration
|
||||
function arrayDeclaration () {
|
||||
var elementFns = [];
|
||||
if (peekToken().text != ']') {
|
||||
do {
|
||||
elementFns.push(expression());
|
||||
} while (expect(','));
|
||||
}
|
||||
consume(']');
|
||||
return function (self){
|
||||
var array = [];
|
||||
for ( var i = 0; i < elementFns.length; i++) {
|
||||
array.push(elementFns[i](self));
|
||||
}
|
||||
return array;
|
||||
};
|
||||
}
|
||||
|
||||
function object () {
|
||||
var keyValues = [];
|
||||
if (peekToken().text != '}') {
|
||||
do {
|
||||
var token = expect(),
|
||||
key = token.string || token.text;
|
||||
consume(":");
|
||||
var value = expression();
|
||||
keyValues.push({key:key, value:value});
|
||||
} while (expect(','));
|
||||
}
|
||||
consume('}');
|
||||
return function (self){
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
function watch () {
|
||||
var decl = [];
|
||||
while(hasTokens()) {
|
||||
decl.push(watchDecl());
|
||||
if (!expect(';')) {
|
||||
assertAllConsumed();
|
||||
}
|
||||
}
|
||||
assertAllConsumed();
|
||||
return function (self){
|
||||
for ( var i = 0; i < decl.length; i++) {
|
||||
var d = decl[i](self);
|
||||
self.addListener(d.name, d.fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function watchDecl () {
|
||||
var anchorName = expect().text;
|
||||
consume(":");
|
||||
var expressionFn;
|
||||
if (peekToken().text == '{') {
|
||||
consume("{");
|
||||
expressionFn = statements();
|
||||
consume("}");
|
||||
} else {
|
||||
expressionFn = expression();
|
||||
}
|
||||
return function(self) {
|
||||
return {name:anchorName, fn:expressionFn};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user