diff options
Diffstat (limited to 'src/Parser.js')
| -rw-r--r-- | src/Parser.js | 345 |
1 files changed, 125 insertions, 220 deletions
diff --git a/src/Parser.js b/src/Parser.js index 5eb75713..eacbf117 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -1,16 +1,8 @@ -function Lexer(text, parsStrings){ - this.text = text; - // UTC dates have 20 characters, we send them through parser - this.dateParseLength = parsStrings ? 20 : -1; - this.tokens = []; - this.index = 0; -} - -Lexer.OPERATORS = { - 'null':function(self){return null;}, +OPERATORS = { + 'null':function(self){return _null;}, 'true':function(self){return true;}, 'false':function(self){return false;}, - 'undefined':noop, + $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;}, @@ -31,149 +23,138 @@ Lexer.OPERATORS = { '|':function(self, a,b){return b(self, a);}, '!':function(self, a){return !a;} }; -Lexer.ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; - -Lexer.prototype = { - peek: function() { - if (this.index + 1 < this.text.length) { - return this.text.charAt(this.index + 1); +ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; + +function lex(text, parseStrings){ + var dateParseLength = parseStrings ? 20 : -1, + tokens = [], + index = 0, + canStartRegExp = true; + + while (index < text.length) { + var ch = text.charAt(index); + if (ch == '"' || ch == "'") { + readString(ch); + canStartRegExp = true; + } else if (ch == '(' || ch == '[') { + tokens.push({index:index, text:ch}); + index++; + } else if (ch == '{' ) { + var peekCh = peek(); + if (peekCh == ':' || peekCh == '(') { + tokens.push({index:index, text:ch + peekCh}); + index++; + } else { + tokens.push({index:index, text:ch}); + } + index++; + canStartRegExp = true; + } else if (ch == ')' || ch == ']' || ch == '}' ) { + tokens.push({index:index, text:ch}); + index++; + canStartRegExp = false; + } else if ( ch == ':' || ch == '.' || ch == ',' || ch == ';') { + tokens.push({index:index, text:ch}); + index++; + canStartRegExp = true; + } else if ( canStartRegExp && ch == '/' ) { + readRegexp(); + canStartRegExp = false; + } else if ( isNumber(ch) ) { + readNumber(); + canStartRegExp = false; + } else if (isIdent(ch)) { + readIdent(); + canStartRegExp = false; + } else if (isWhitespace(ch)) { + index++; } else { - return false; - } - }, - - parse: function() { - var tokens = this.tokens; - var OPERATORS = Lexer.OPERATORS; - var canStartRegExp = true; - while (this.index < this.text.length) { - var ch = this.text.charAt(this.index); - if (ch == '"' || ch == "'") { - this.readString(ch); - canStartRegExp = true; - } else if (ch == '(' || ch == '[') { - tokens.push({index:this.index, text:ch}); - this.index++; - } else if (ch == '{' ) { - var peekCh = this.peek(); - if (peekCh == ':' || peekCh == '(') { - tokens.push({index:this.index, text:ch + peekCh}); - this.index++; - } else { - tokens.push({index:this.index, text:ch}); - } - this.index++; - canStartRegExp = true; - } else if (ch == ')' || ch == ']' || ch == '}' ) { - tokens.push({index:this.index, text:ch}); - this.index++; - canStartRegExp = false; - } else if ( ch == ':' || ch == '.' || ch == ',' || ch == ';') { - tokens.push({index:this.index, text:ch}); - this.index++; - canStartRegExp = true; - } else if ( canStartRegExp && ch == '/' ) { - this.readRegexp(); - canStartRegExp = false; - } else if ( this.isNumber(ch) ) { - this.readNumber(); - canStartRegExp = false; - } else if (this.isIdent(ch)) { - this.readIdent(); - canStartRegExp = false; - } else if (this.isWhitespace(ch)) { - this.index++; + 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}); + index += 1; } else { - var ch2 = ch + this.peek(); - var fn = OPERATORS[ch]; - var fn2 = OPERATORS[ch2]; - if (fn2) { - tokens.push({index:this.index, text:ch2, fn:fn2}); - this.index += 2; - } else if (fn) { - tokens.push({index:this.index, text:ch, fn:fn}); - this.index += 1; - } else { - throw "Lexer Error: Unexpected next character [" + - this.text.substring(this.index) + - "] in expression '" + this.text + - "' at column '" + (this.index+1) + "'."; - } - canStartRegExp = true; + throw "Lexer Error: Unexpected next character [" + + text.substring(index) + + "] in expression '" + text + + "' at column '" + (index+1) + "'."; } + canStartRegExp = true; } - return tokens; - }, + } + return tokens; - isNumber: function(ch) { + function peek() { + return index + 1 < text.length ? text.charAt(index + 1) : false; + } + function isNumber(ch) { return '0' <= ch && ch <= '9'; - }, - - isWhitespace: function(ch) { + } + function isWhitespace(ch) { return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n' || ch == '\v'; - }, - - isIdent: function(ch) { + } + function isIdent(ch) { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '_' == ch || ch == '$'; - }, - - readNumber: function() { + } + function readNumber() { var number = ""; - var start = this.index; - while (this.index < this.text.length) { - var ch = this.text.charAt(this.index); - if (ch == '.' || this.isNumber(ch)) { + var start = index; + while (index < text.length) { + var ch = text.charAt(index); + if (ch == '.' || isNumber(ch)) { number += ch; } else { break; } - this.index++; + index++; } number = 1 * number; - this.tokens.push({index:start, text:number, + tokens.push({index:start, text:number, fn:function(){return number;}}); - }, - - readIdent: function() { + } + function readIdent() { var ident = ""; - var start = this.index; - while (this.index < this.text.length) { - var ch = this.text.charAt(this.index); - if (ch == '.' || this.isIdent(ch) || this.isNumber(ch)) { + var start = index; + while (index < text.length) { + var ch = text.charAt(index); + if (ch == '.' || isIdent(ch) || isNumber(ch)) { ident += ch; } else { break; } - this.index++; + index++; } - var fn = Lexer.OPERATORS[ident]; + var fn = OPERATORS[ident]; if (!fn) { fn = getterFn(ident); fn.isAssignable = ident; } - this.tokens.push({index:start, text:ident, fn:fn}); - }, - - readString: function(quote) { - var start = this.index; - var dateParseLength = this.dateParseLength; - this.index++; + tokens.push({index:start, text:ident, fn:fn}); + } + function readString(quote) { + var start = index; + index++; var string = ""; var rawString = quote; var escape = false; - while (this.index < this.text.length) { - var ch = this.text.charAt(this.index); + while (index < text.length) { + var ch = text.charAt(index); rawString += ch; if (escape) { if (ch == 'u') { - var hex = this.text.substring(this.index + 1, this.index + 5); - this.index += 4; + var hex = text.substring(index + 1, index + 5); + index += 4; string += String.fromCharCode(parseInt(hex, 16)); } else { - var rep = Lexer.ESCAPE[ch]; + var rep = ESCAPE[ch]; if (rep) { string += rep; } else { @@ -184,8 +165,8 @@ Lexer.prototype = { } else if (ch == '\\') { escape = true; } else if (ch == quote) { - this.index++; - this.tokens.push({index:start, text:rawString, string:string, + index++; + tokens.push({index:start, text:rawString, string:string, fn:function(){ return (string.length == dateParseLength) ? angular['String']['toDate'](string) : string; @@ -194,20 +175,19 @@ Lexer.prototype = { } else { string += ch; } - this.index++; + index++; } throw "Lexer Error: Unterminated quote [" + - this.text.substring(start) + "] starting at column '" + - (start+1) + "' in expression '" + this.text + "'."; - }, - - readRegexp: function(quote) { - var start = this.index; - this.index++; + text.substring(start) + "] starting at column '" + + (start+1) + "' in expression '" + text + "'."; + } + function readRegexp(quote) { + var start = index; + index++; var regexp = ""; var escape = false; - while (this.index < this.text.length) { - var ch = this.text.charAt(this.index); + while (index < text.length) { + var ch = text.charAt(index); if (escape) { regexp += ch; escape = false; @@ -215,36 +195,36 @@ Lexer.prototype = { regexp += ch; escape = true; } else if (ch === '/') { - this.index++; + index++; var flags = ""; - if (this.isIdent(this.text.charAt(this.index))) { - this.readIdent(); - flags = this.tokens.pop().text; + if (isIdent(text.charAt(index))) { + readIdent(); + flags = tokens.pop().text; } var compiledRegexp = new RegExp(regexp, flags); - this.tokens.push({index:start, text:regexp, flags:flags, + tokens.push({index:start, text:regexp, flags:flags, fn:function(){return compiledRegexp;}}); return; } else { regexp += ch; } - this.index++; + index++; } throw "Lexer Error: Unterminated RegExp [" + - this.text.substring(start) + "] starting at column '" + - (start+1) + "' in expression '" + this.text + "'."; + text.substring(start) + "] starting at column '" + + (start+1) + "' in expression '" + text + "'."; } -}; +} ///////////////////////////////////////// function Parser(text, parseStrings){ this.text = text; - this.tokens = new Lexer(text, parseStrings).parse(); + this.tokens = lex(text, parseStrings); this.index = 0; } -Parser.ZERO = function(){ +ZERO = function(){ return 0; }; @@ -472,7 +452,7 @@ Parser.prototype = { if (this.expect('+')) { return this.primary(); } else if (token = this.expect('-')) { - return this._binary(Parser.ZERO, token.fn, this.unary()); + return this._binary(ZERO, token.fn, this.unary()); } else if (token = this.expect('!')) { return this._unary(token.fn, this.unary()); } else { @@ -490,7 +470,7 @@ Parser.prototype = { if (instance) instance = instance[key]; } - if (typeof instance != 'function') { + if (typeof instance != $function) { throw "Function '" + token.text + "' at column '" + (token.index+1) + "' in '" + this.text + "' is not defined."; } @@ -507,10 +487,6 @@ Parser.prototype = { primary = this.arrayDeclaration(); } else if (this.expect('{')) { primary = this.object(); - } else if (this.expect('{:')) { - primary = this.closure(false); - } else if (this.expect('{(')) { - primary = this.closure(true); } else { var token = this.expect(); primary = token.fn; @@ -533,32 +509,6 @@ Parser.prototype = { return primary; }, - closure: function(hasArgs) { - var args = []; - if (hasArgs) { - if (!this.expect(')')) { - args.push(this.expect().text); - while(this.expect(',')) { - args.push(this.expect().text); - } - this.consume(')'); - } - this.consume(":"); - } - var statements = this.statements(); - this.consume("}"); - return function(self) { - return function($){ - var scope = createScope(self); - scope['$'] = $; - for ( var i = 0; i < args.length; i++) { - setter(scope, args[i], arguments[i]); - } - return statements(scope); - }; - }; - }, - fieldAccess: function(object) { var field = this.expect().text; var getter = getterFn(field); @@ -581,7 +531,7 @@ Parser.prototype = { return function (self){ var o = obj(self); var i = indexFn(self); - return (o) ? o[i] : undefined; + return (o) ? o[i] : _undefined; }; } }, @@ -601,8 +551,8 @@ Parser.prototype = { } var fnPtr = fn(self) || noop; // IE stupidity! - return fnPtr.apply ? - fnPtr.apply(self, args) : + return fnPtr.apply ? + fnPtr.apply(self, args) : fnPtr(args[0], args[1], args[2], args[3], args[4]); }; }, @@ -648,51 +598,6 @@ Parser.prototype = { }; }, - entityDeclaration: function () { - var decl = []; - while(this.hasTokens()) { - decl.push(this.entityDecl()); - if (!this.expect(';')) { - this.assertAllConsumed(); - } - } - return function (self){ - var code = ""; - for ( var i = 0; i < decl.length; i++) { - code += decl[i](self); - } - return code; - }; - }, - - entityDecl: function () { - var entity = this.expect().text; - var instance; - var defaults; - if (this.expect('=')) { - instance = entity; - entity = this.expect().text; - } - if (this.expect(':')) { - defaults = this.primary()(null); - } - return function(self) { - var Entity = self.datastore.entity(entity, defaults); - setter(self, entity, Entity); - if (instance) { - var document = Entity(); - document['$$anchor'] = instance; - setter(self, instance, document); - return "$anchor." + instance + ":{" + - instance + "=" + entity + ".load($anchor." + instance + ");" + - instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + - "};"; - } else { - return ""; - } - }; - }, - watch: function () { var decl = []; while(this.hasTokens()) { |
