diff options
Diffstat (limited to 'src/parser.js')
| -rw-r--r-- | src/parser.js | 663 |
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}; + }; + } +} + + + + |
