diff options
| author | Igor Minar | 2010-10-27 15:20:05 -0700 | 
|---|---|---|
| committer | Igor Minar | 2010-10-27 15:32:30 -0700 | 
| commit | c67af8a03819004c4aaa775805badd1e631af738 (patch) | |
| tree | 7426c057fd9bffd1a25fc9c191732d2e292716fe /src/parser.js | |
| parent | 2da1de5a6da0cedf490c54509487c27ba018585a (diff) | |
| download | angular.js-c67af8a03819004c4aaa775805badd1e631af738.tar.bz2 | |
rename src/Parser.js to src/parser.js
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}; +    }; +  } +} + + + +  | 
