aboutsummaryrefslogtreecommitdiffstats
path: root/src/parser.js
diff options
context:
space:
mode:
authorMisko Hevery2011-11-02 21:43:56 -0700
committerMisko Hevery2011-11-14 16:39:33 -0800
commitbee6060e4b2499b385465e20f7a0dc2d11f003c0 (patch)
treea005843abd4f741694ca88dfced03a7981d66e16 /src/parser.js
parent16597e8b52bdfe34b2239a5ab86a839fa8e980d6 (diff)
downloadangular.js-bee6060e4b2499b385465e20f7a0dc2d11f003c0.tar.bz2
move(parser): appease the History God
Diffstat (limited to 'src/parser.js')
-rw-r--r--src/parser.js739
1 files changed, 0 insertions, 739 deletions
diff --git a/src/parser.js b/src/parser.js
deleted file mode 100644
index 41fff7d5..00000000
--- a/src/parser.js
+++ /dev/null
@@ -1,739 +0,0 @@
-'use strict';
-
-var OPERATORS = {
- 'null':function(self){return null;},
- 'true':function(self){return true;},
- 'false':function(self){return false;},
- $undefined:noop,
- '+':function(self, a,b){a=a(self); b=b(self); return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
- '-':function(self, a,b){a=a(self); b=b(self); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
- '*':function(self, a,b){return a(self)*b(self);},
- '/':function(self, a,b){return a(self)/b(self);},
- '%':function(self, a,b){return a(self)%b(self);},
- '^':function(self, a,b){return a(self)^b(self);},
- '=':noop,
- '==':function(self, a,b){return a(self)==b(self);},
- '!=':function(self, a,b){return a(self)!=b(self);},
- '<':function(self, a,b){return a(self)<b(self);},
- '>':function(self, a,b){return a(self)>b(self);},
- '<=':function(self, a,b){return a(self)<=b(self);},
- '>=':function(self, a,b){return a(self)>=b(self);},
- '&&':function(self, a,b){return a(self)&&b(self);},
- '||':function(self, a,b){return a(self)||b(self);},
- '&':function(self, a,b){return a(self)&b(self);},
-// '|':function(self, a,b){return a|b;},
- '|':function(self, a,b){return b(self)(self, a(self));},
- '!':function(self, a){return !a(self);}
-};
-var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
-
-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();
- // 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('(){}[].,;:')) {
- tokens.push({
- index:index,
- text:ch,
- json:(was(':[,') && is('{[')) || 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 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;
- }
- 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);
- }
-}
-
-/////////////////////////////////////////
-
-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}); };
- }
- //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)
- };
-
- 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) + "].");
- }
-
- 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) {
- throwError("is not valid json", token);
- }
- tokens.shift();
- 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);
- };
- }
-
- function binaryFn(left, fn, right) {
- return function(self) {
- return fn(self, left, right);
- };
- }
-
- function hasTokens () {
- return tokens.length > 0;
- }
-
- function statements() {
- 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;
- };
- }
- }
- }
-
- 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 _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 (!isFunction(instance)) {
- 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;
- };
- }
-
- 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};
- };
- }
-}
-
-//////////////////////////////////////////////////
-// 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)) {\n' +
- ' fn=function(){ return l' + key + '.apply(l, arguments); };\n' +
- ' fn.$unboundFn=s;\n' +
- ' s=fn;\n' +
- '}\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): Deprecate? Remove!
-// I think that compilation should be a service.
-function expressionCompile(exp) {
- if (isFunction(exp)) return exp;
- var fn = compileCache[exp];
- if (!fn) {
- fn = compileCache[exp] = parser(exp).statements();
- }
- return fn;
-}