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;}, '=':noop, '==':function(self, a,b){return a==b;}, '!=':function(self, a,b){return a!=b;}, '<':function(self, a,b){return a':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 ? 24 : -1, tokens = [], token, index = 0, json = [], ch, lastCh = ':'; 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(); 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 { throwError("Unexpected next character ", index, 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 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 consume(regexp, processToken, errorMsg) { var match = text.substr(index).match(regexp); var token = {index: index}; var start = index; if (!match) throwError(errorMsg); index += match[0].length; processToken(token, token.text = match[0], start); tokens.push(token); } function readNumber() { consume(/^(\d+)?(\.\d+)?([eE][+-]?\d+)?/, function(token, number){ token.text = number = 1 * number; token.json = true; token.fn = valueFn(number); }, "Not a valid number"); } function readIdent() { consume(/^[\w_\$][\w_\$\d]*(\.[\w_\$][\w_\$\d]*)*/, function(token, ident){ fn = OPERATORS[ident]; if (!fn) { fn = getterFn(ident); fn.isAssignable = ident; } token.fn = OPERATORS[ident]||extend(getterFn(ident), { assign:function(self, value){ return setter(self, ident, value); } }); token.json = OPERATORS[ident]; }); } function readString(quote) { consume(/^(('(\\'|[^'])*')|("(\\"|[^"])*"))/, function(token, rawString, start){ var hasError; var string = token.string = rawString.substr(1, rawString.length - 2). replace(/(\\u(.?.?.?.?))|(\\(.))/g, function(match, wholeUnicode, unicode, wholeEscape, escape){ if (unicode && !unicode.match(/[\da-fA-F]{4}/)) hasError = hasError || bind(null, throwError, "Invalid unicode escape [\\u" + unicode + "]", start); return unicode ? String.fromCharCode(parseInt(unicode, 16)) : ESCAPE[escape] || escape; }); (hasError||noop)(); token.json = true; token.fn = function(){ return (string.length == dateParseLength) ? angular['String']['toDate'](string) : string; }; }, "Unterminated string"); } } ///////////////////////////////////////// function parser(text, json){ var ZERO = valueFn(0), tokens = lex(text, json); return { assertAllConsumed: assertAllConsumed, primary: primary, statements: statements, validator: validator, filter: filter, //TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular) watch: watch }; /////////////////////////////////// function throwError(msg, token) { throw Error("Parse Error: Token '" + token.text + "' " + msg + " at column " + (token.index + 1) + " of expression [" + text + "] starting at [" + text.substring(token.index) + "]."); } function peekToken() { if (tokens.length === 0) throw Error("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; throwError("is not valid json", token); } tokens.shift(); this.currentToken = token; return token; } return false; } function consume(e1){ if (!expect(e1)) { throwError("is unexpected, expecting [" + e1 + "]", peek()); } } 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) { throwError("is extra token not part of expression", tokens[0]); } } 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 assignment(); } function assignment(){ var left = logicalOR(); var right; var token; if (token = expect('=')) { if (!left.assign) { throwError("implies assignment but [" + text.substring(0, token.index) + "] can not be assigned to", token); } right = logicalOR(); return function(self){ return left.assign(self, right(self)); }; } 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) { throwError("should be a function", token); } 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) { throwError("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 { throwError("IMPOSSIBLE"); } } return primary; } 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); } }); } function objectIndex(obj) { var indexFn = expression(); consume(']'); return extend( function (self){ var o = obj(self); var i = indexFn(self); return (o) ? o[i] : _undefined; }, { assign:function(self, value){ return obj(self)[indexFn(self)] = value; } }); } 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; }; } //TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular) 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}; }; } }