aboutsummaryrefslogtreecommitdiffstats
path: root/src/parser.js
diff options
context:
space:
mode:
authorIgor Minar2010-10-27 15:20:05 -0700
committerIgor Minar2010-10-27 15:32:30 -0700
commitc67af8a03819004c4aaa775805badd1e631af738 (patch)
tree7426c057fd9bffd1a25fc9c191732d2e292716fe /src/parser.js
parent2da1de5a6da0cedf490c54509487c27ba018585a (diff)
downloadangular.js-c67af8a03819004c4aaa775805badd1e631af738.tar.bz2
rename src/Parser.js to src/parser.js
Diffstat (limited to 'src/parser.js')
-rw-r--r--src/parser.js663
1 files changed, 663 insertions, 0 deletions
diff --git a/src/parser.js b/src/parser.js
new file mode 100644
index 00000000..8dc8f9c2
--- /dev/null
+++ b/src/parser.js
@@ -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};
+ };
+ }
+}
+
+
+
+