jmespath.js
   1  (function(exports) {
   2    "use strict";
   3  
   4    function isArray(obj) {
   5      if (obj !== null) {
   6        return Object.prototype.toString.call(obj) === "[object Array]";
   7      } else {
   8        return false;
   9      }
  10    }
  11  
  12    function isObject(obj) {
  13      if (obj !== null) {
  14        return Object.prototype.toString.call(obj) === "[object Object]";
  15      } else {
  16        return false;
  17      }
  18    }
  19  
  20    function strictDeepEqual(first, second) {
  21      // Check the scalar case first.
  22      if (first === second) {
  23        return true;
  24      }
  25  
  26      // Check if they are the same type.
  27      var firstType = Object.prototype.toString.call(first);
  28      if (firstType !== Object.prototype.toString.call(second)) {
  29        return false;
  30      }
  31      // We know that first and second have the same type so we can just check the
  32      // first type from now on.
  33      if (isArray(first) === true) {
  34        // Short circuit if they're not the same length;
  35        if (first.length !== second.length) {
  36          return false;
  37        }
  38        for (var i = 0; i < first.length; i++) {
  39          if (strictDeepEqual(first[i], second[i]) === false) {
  40            return false;
  41          }
  42        }
  43        return true;
  44      }
  45      if (isObject(first) === true) {
  46        // An object is equal if it has the same key/value pairs.
  47        var keysSeen = {};
  48        for (var key in first) {
  49          if (hasOwnProperty.call(first, key)) {
  50            if (strictDeepEqual(first[key], second[key]) === false) {
  51              return false;
  52            }
  53            keysSeen[key] = true;
  54          }
  55        }
  56        // Now check that there aren't any keys in second that weren't
  57        // in first.
  58        for (var key2 in second) {
  59          if (hasOwnProperty.call(second, key2)) {
  60            if (keysSeen[key2] !== true) {
  61              return false;
  62            }
  63          }
  64        }
  65        return true;
  66      }
  67      return false;
  68    }
  69  
  70    function isFalse(obj) {
  71      // From the spec:
  72      // A false value corresponds to the following values:
  73      // Empty list
  74      // Empty object
  75      // Empty string
  76      // False boolean
  77      // null value
  78  
  79      // First check the scalar values.
  80      if (obj === "" || obj === false || obj === null) {
  81          return true;
  82      } else if (isArray(obj) && obj.length === 0) {
  83          // Check for an empty array.
  84          return true;
  85      } else if (isObject(obj)) {
  86          // Check for an empty object.
  87          for (var key in obj) {
  88              // If there are any keys, then
  89              // the object is not empty so the object
  90              // is not false.
  91              if (obj.hasOwnProperty(key)) {
  92                return false;
  93              }
  94          }
  95          return true;
  96      } else {
  97          return false;
  98      }
  99    }
 100  
 101    function objValues(obj) {
 102      var keys = Object.keys(obj);
 103      var values = [];
 104      for (var i = 0; i < keys.length; i++) {
 105        values.push(obj[keys[i]]);
 106      }
 107      return values;
 108    }
 109  
 110    function merge(a, b) {
 111        var merged = {};
 112        for (var key in a) {
 113            merged[key] = a[key];
 114        }
 115        for (var key2 in b) {
 116            merged[key2] = b[key2];
 117        }
 118        return merged;
 119    }
 120  
 121    var trimLeft;
 122    if (typeof String.prototype.trimLeft === "function") {
 123      trimLeft = function(str) {
 124        return str.trimLeft();
 125      };
 126    } else {
 127      trimLeft = function(str) {
 128        return str.match(/^\s*(.*)/)[1];
 129      };
 130    }
 131  
 132    // Type constants used to define functions.
 133    var TYPE_NUMBER = 0;
 134    var TYPE_ANY = 1;
 135    var TYPE_STRING = 2;
 136    var TYPE_ARRAY = 3;
 137    var TYPE_OBJECT = 4;
 138    var TYPE_BOOLEAN = 5;
 139    var TYPE_EXPREF = 6;
 140    var TYPE_NULL = 7;
 141    var TYPE_ARRAY_NUMBER = 8;
 142    var TYPE_ARRAY_STRING = 9;
 143  
 144    var TOK_EOF = "EOF";
 145    var TOK_UNQUOTEDIDENTIFIER = "UnquotedIdentifier";
 146    var TOK_QUOTEDIDENTIFIER = "QuotedIdentifier";
 147    var TOK_RBRACKET = "Rbracket";
 148    var TOK_RPAREN = "Rparen";
 149    var TOK_COMMA = "Comma";
 150    var TOK_COLON = "Colon";
 151    var TOK_RBRACE = "Rbrace";
 152    var TOK_NUMBER = "Number";
 153    var TOK_CURRENT = "Current";
 154    var TOK_EXPREF = "Expref";
 155    var TOK_PIPE = "Pipe";
 156    var TOK_OR = "Or";
 157    var TOK_AND = "And";
 158    var TOK_EQ = "EQ";
 159    var TOK_GT = "GT";
 160    var TOK_LT = "LT";
 161    var TOK_GTE = "GTE";
 162    var TOK_LTE = "LTE";
 163    var TOK_NE = "NE";
 164    var TOK_FLATTEN = "Flatten";
 165    var TOK_STAR = "Star";
 166    var TOK_FILTER = "Filter";
 167    var TOK_DOT = "Dot";
 168    var TOK_NOT = "Not";
 169    var TOK_LBRACE = "Lbrace";
 170    var TOK_LBRACKET = "Lbracket";
 171    var TOK_LPAREN= "Lparen";
 172    var TOK_LITERAL= "Literal";
 173  
 174    // The "&", "[", "<", ">" tokens
 175    // are not in basicToken because
 176    // there are two token variants
 177    // ("&&", "[?", "<=", ">=").  This is specially handled
 178    // below.
 179  
 180    var basicTokens = {
 181      ".": TOK_DOT,
 182      "*": TOK_STAR,
 183      ",": TOK_COMMA,
 184      ":": TOK_COLON,
 185      "{": TOK_LBRACE,
 186      "}": TOK_RBRACE,
 187      "]": TOK_RBRACKET,
 188      "(": TOK_LPAREN,
 189      ")": TOK_RPAREN,
 190      "@": TOK_CURRENT
 191    };
 192  
 193    var operatorStartToken = {
 194        "<": true,
 195        ">": true,
 196        "=": true,
 197        "!": true
 198    };
 199  
 200    var skipChars = {
 201        " ": true,
 202        "\t": true,
 203        "\n": true
 204    };
 205  
 206  
 207    function isAlpha(ch) {
 208        return (ch >= "a" && ch <= "z") ||
 209               (ch >= "A" && ch <= "Z") ||
 210               ch === "_";
 211    }
 212  
 213    function isNum(ch) {
 214        return (ch >= "0" && ch <= "9") ||
 215               ch === "-";
 216    }
 217    function isAlphaNum(ch) {
 218        return (ch >= "a" && ch <= "z") ||
 219               (ch >= "A" && ch <= "Z") ||
 220               (ch >= "0" && ch <= "9") ||
 221               ch === "_";
 222    }
 223  
 224    function Lexer() {
 225    }
 226    Lexer.prototype = {
 227        tokenize: function(stream) {
 228            var tokens = [];
 229            this._current = 0;
 230            var start;
 231            var identifier;
 232            var token;
 233            while (this._current < stream.length) {
 234                if (isAlpha(stream[this._current])) {
 235                    start = this._current;
 236                    identifier = this._consumeUnquotedIdentifier(stream);
 237                    tokens.push({type: TOK_UNQUOTEDIDENTIFIER,
 238                                 value: identifier,
 239                                 start: start});
 240                } else if (basicTokens[stream[this._current]] !== undefined) {
 241                    tokens.push({type: basicTokens[stream[this._current]],
 242                                value: stream[this._current],
 243                                start: this._current});
 244                    this._current++;
 245                } else if (isNum(stream[this._current])) {
 246                    token = this._consumeNumber(stream);
 247                    tokens.push(token);
 248                } else if (stream[this._current] === "[") {
 249                    // No need to increment this._current.  This happens
 250                    // in _consumeLBracket
 251                    token = this._consumeLBracket(stream);
 252                    tokens.push(token);
 253                } else if (stream[this._current] === "\"") {
 254                    start = this._current;
 255                    identifier = this._consumeQuotedIdentifier(stream);
 256                    tokens.push({type: TOK_QUOTEDIDENTIFIER,
 257                                 value: identifier,
 258                                 start: start});
 259                } else if (stream[this._current] === "'") {
 260                    start = this._current;
 261                    identifier = this._consumeRawStringLiteral(stream);
 262                    tokens.push({type: TOK_LITERAL,
 263                                 value: identifier,
 264                                 start: start});
 265                } else if (stream[this._current] === "`") {
 266                    start = this._current;
 267                    var literal = this._consumeLiteral(stream);
 268                    tokens.push({type: TOK_LITERAL,
 269                                 value: literal,
 270                                 start: start});
 271                } else if (operatorStartToken[stream[this._current]] !== undefined) {
 272                    tokens.push(this._consumeOperator(stream));
 273                } else if (skipChars[stream[this._current]] !== undefined) {
 274                    // Ignore whitespace.
 275                    this._current++;
 276                } else if (stream[this._current] === "&") {
 277                    start = this._current;
 278                    this._current++;
 279                    if (stream[this._current] === "&") {
 280                        this._current++;
 281                        tokens.push({type: TOK_AND, value: "&&", start: start});
 282                    } else {
 283                        tokens.push({type: TOK_EXPREF, value: "&", start: start});
 284                    }
 285                } else if (stream[this._current] === "|") {
 286                    start = this._current;
 287                    this._current++;
 288                    if (stream[this._current] === "|") {
 289                        this._current++;
 290                        tokens.push({type: TOK_OR, value: "||", start: start});
 291                    } else {
 292                        tokens.push({type: TOK_PIPE, value: "|", start: start});
 293                    }
 294                } else {
 295                    var error = new Error("Unknown character:" + stream[this._current]);
 296                    error.name = "LexerError";
 297                    throw error;
 298                }
 299            }
 300            return tokens;
 301        },
 302  
 303        _consumeUnquotedIdentifier: function(stream) {
 304            var start = this._current;
 305            this._current++;
 306            while (this._current < stream.length && isAlphaNum(stream[this._current])) {
 307                this._current++;
 308            }
 309            return stream.slice(start, this._current);
 310        },
 311  
 312        _consumeQuotedIdentifier: function(stream) {
 313            var start = this._current;
 314            this._current++;
 315            var maxLength = stream.length;
 316            while (stream[this._current] !== "\"" && this._current < maxLength) {
 317                // You can escape a double quote and you can escape an escape.
 318                var current = this._current;
 319                if (stream[current] === "\\" && (stream[current + 1] === "\\" ||
 320                                                 stream[current + 1] === "\"")) {
 321                    current += 2;
 322                } else {
 323                    current++;
 324                }
 325                this._current = current;
 326            }
 327            this._current++;
 328            return JSON.parse(stream.slice(start, this._current));
 329        },
 330  
 331        _consumeRawStringLiteral: function(stream) {
 332            var start = this._current;
 333            this._current++;
 334            var maxLength = stream.length;
 335            while (stream[this._current] !== "'" && this._current < maxLength) {
 336                // You can escape a single quote and you can escape an escape.
 337                var current = this._current;
 338                if (stream[current] === "\\" && (stream[current + 1] === "\\" ||
 339                                                 stream[current + 1] === "'")) {
 340                    current += 2;
 341                } else {
 342                    current++;
 343                }
 344                this._current = current;
 345            }
 346            this._current++;
 347            var literal = stream.slice(start + 1, this._current - 1);
 348            return literal.replace("\\'", "'");
 349        },
 350  
 351        _consumeNumber: function(stream) {
 352            var start = this._current;
 353            this._current++;
 354            var maxLength = stream.length;
 355            while (isNum(stream[this._current]) && this._current < maxLength) {
 356                this._current++;
 357            }
 358            var value = parseInt(stream.slice(start, this._current));
 359            return {type: TOK_NUMBER, value: value, start: start};
 360        },
 361  
 362        _consumeLBracket: function(stream) {
 363            var start = this._current;
 364            this._current++;
 365            if (stream[this._current] === "?") {
 366                this._current++;
 367                return {type: TOK_FILTER, value: "[?", start: start};
 368            } else if (stream[this._current] === "]") {
 369                this._current++;
 370                return {type: TOK_FLATTEN, value: "[]", start: start};
 371            } else {
 372                return {type: TOK_LBRACKET, value: "[", start: start};
 373            }
 374        },
 375  
 376        _consumeOperator: function(stream) {
 377            var start = this._current;
 378            var startingChar = stream[start];
 379            this._current++;
 380            if (startingChar === "!") {
 381                if (stream[this._current] === "=") {
 382                    this._current++;
 383                    return {type: TOK_NE, value: "!=", start: start};
 384                } else {
 385                  return {type: TOK_NOT, value: "!", start: start};
 386                }
 387            } else if (startingChar === "<") {
 388                if (stream[this._current] === "=") {
 389                    this._current++;
 390                    return {type: TOK_LTE, value: "<=", start: start};
 391                } else {
 392                    return {type: TOK_LT, value: "<", start: start};
 393                }
 394            } else if (startingChar === ">") {
 395                if (stream[this._current] === "=") {
 396                    this._current++;
 397                    return {type: TOK_GTE, value: ">=", start: start};
 398                } else {
 399                    return {type: TOK_GT, value: ">", start: start};
 400                }
 401            } else if (startingChar === "=") {
 402                if (stream[this._current] === "=") {
 403                    this._current++;
 404                    return {type: TOK_EQ, value: "==", start: start};
 405                }
 406            }
 407        },
 408  
 409        _consumeLiteral: function(stream) {
 410            this._current++;
 411            var start = this._current;
 412            var maxLength = stream.length;
 413            var literal;
 414            while(stream[this._current] !== "`" && this._current < maxLength) {
 415                // You can escape a literal char or you can escape the escape.
 416                var current = this._current;
 417                if (stream[current] === "\\" && (stream[current + 1] === "\\" ||
 418                                                 stream[current + 1] === "`")) {
 419                    current += 2;
 420                } else {
 421                    current++;
 422                }
 423                this._current = current;
 424            }
 425            var literalString = trimLeft(stream.slice(start, this._current));
 426            literalString = literalString.replace("\\`", "`");
 427            if (this._looksLikeJSON(literalString)) {
 428                literal = JSON.parse(literalString);
 429            } else {
 430                // Try to JSON parse it as "<literal>"
 431                literal = JSON.parse("\"" + literalString + "\"");
 432            }
 433            // +1 gets us to the ending "`", +1 to move on to the next char.
 434            this._current++;
 435            return literal;
 436        },
 437  
 438        _looksLikeJSON: function(literalString) {
 439            var startingChars = "[{\"";
 440            var jsonLiterals = ["true", "false", "null"];
 441            var numberLooking = "-0123456789";
 442  
 443            if (literalString === "") {
 444                return false;
 445            } else if (startingChars.indexOf(literalString[0]) >= 0) {
 446                return true;
 447            } else if (jsonLiterals.indexOf(literalString) >= 0) {
 448                return true;
 449            } else if (numberLooking.indexOf(literalString[0]) >= 0) {
 450                try {
 451                    JSON.parse(literalString);
 452                    return true;
 453                } catch (ex) {
 454                    return false;
 455                }
 456            } else {
 457                return false;
 458            }
 459        }
 460    };
 461  
 462        var bindingPower = {};
 463        bindingPower[TOK_EOF] = 0;
 464        bindingPower[TOK_UNQUOTEDIDENTIFIER] = 0;
 465        bindingPower[TOK_QUOTEDIDENTIFIER] = 0;
 466        bindingPower[TOK_RBRACKET] = 0;
 467        bindingPower[TOK_RPAREN] = 0;
 468        bindingPower[TOK_COMMA] = 0;
 469        bindingPower[TOK_RBRACE] = 0;
 470        bindingPower[TOK_NUMBER] = 0;
 471        bindingPower[TOK_CURRENT] = 0;
 472        bindingPower[TOK_EXPREF] = 0;
 473        bindingPower[TOK_PIPE] = 1;
 474        bindingPower[TOK_OR] = 2;
 475        bindingPower[TOK_AND] = 3;
 476        bindingPower[TOK_EQ] = 5;
 477        bindingPower[TOK_GT] = 5;
 478        bindingPower[TOK_LT] = 5;
 479        bindingPower[TOK_GTE] = 5;
 480        bindingPower[TOK_LTE] = 5;
 481        bindingPower[TOK_NE] = 5;
 482        bindingPower[TOK_FLATTEN] = 9;
 483        bindingPower[TOK_STAR] = 20;
 484        bindingPower[TOK_FILTER] = 21;
 485        bindingPower[TOK_DOT] = 40;
 486        bindingPower[TOK_NOT] = 45;
 487        bindingPower[TOK_LBRACE] = 50;
 488        bindingPower[TOK_LBRACKET] = 55;
 489        bindingPower[TOK_LPAREN] = 60;
 490  
 491    function Parser() {
 492    }
 493  
 494    Parser.prototype = {
 495        parse: function(expression) {
 496            this._loadTokens(expression);
 497            this.index = 0;
 498            var ast = this.expression(0);
 499            if (this._lookahead(0) !== TOK_EOF) {
 500                var t = this._lookaheadToken(0);
 501                var error = new Error(
 502                    "Unexpected token type: " + t.type + ", value: " + t.value);
 503                error.name = "ParserError";
 504                throw error;
 505            }
 506            return ast;
 507        },
 508  
 509        _loadTokens: function(expression) {
 510            var lexer = new Lexer();
 511            var tokens = lexer.tokenize(expression);
 512            tokens.push({type: TOK_EOF, value: "", start: expression.length});
 513            this.tokens = tokens;
 514        },
 515  
 516        expression: function(rbp) {
 517            var leftToken = this._lookaheadToken(0);
 518            this._advance();
 519            var left = this.nud(leftToken);
 520            var currentToken = this._lookahead(0);
 521            while (rbp < bindingPower[currentToken]) {
 522                this._advance();
 523                left = this.led(currentToken, left);
 524                currentToken = this._lookahead(0);
 525            }
 526            return left;
 527        },
 528  
 529        _lookahead: function(number) {
 530            return this.tokens[this.index + number].type;
 531        },
 532  
 533        _lookaheadToken: function(number) {
 534            return this.tokens[this.index + number];
 535        },
 536  
 537        _advance: function() {
 538            this.index++;
 539        },
 540  
 541        nud: function(token) {
 542          var left;
 543          var right;
 544          var expression;
 545          switch (token.type) {
 546            case TOK_LITERAL:
 547              return {type: "Literal", value: token.value};
 548            case TOK_UNQUOTEDIDENTIFIER:
 549              return {type: "Field", name: token.value};
 550            case TOK_QUOTEDIDENTIFIER:
 551              var node = {type: "Field", name: token.value};
 552              if (this._lookahead(0) === TOK_LPAREN) {
 553                  throw new Error("Quoted identifier not allowed for function names.");
 554              } else {
 555                  return node;
 556              }
 557              break;
 558            case TOK_NOT:
 559              right = this.expression(bindingPower.Not);
 560              return {type: "NotExpression", children: [right]};
 561            case TOK_STAR:
 562              left = {type: "Identity"};
 563              right = null;
 564              if (this._lookahead(0) === TOK_RBRACKET) {
 565                  // This can happen in a multiselect,
 566                  // [a, b, *]
 567                  right = {type: "Identity"};
 568              } else {
 569                  right = this._parseProjectionRHS(bindingPower.Star);
 570              }
 571              return {type: "ValueProjection", children: [left, right]};
 572            case TOK_FILTER:
 573              return this.led(token.type, {type: "Identity"});
 574            case TOK_LBRACE:
 575              return this._parseMultiselectHash();
 576            case TOK_FLATTEN:
 577              left = {type: TOK_FLATTEN, children: [{type: "Identity"}]};
 578              right = this._parseProjectionRHS(bindingPower.Flatten);
 579              return {type: "Projection", children: [left, right]};
 580            case TOK_LBRACKET:
 581              if (this._lookahead(0) === TOK_NUMBER || this._lookahead(0) === TOK_COLON) {
 582                  right = this._parseIndexExpression();
 583                  return this._projectIfSlice({type: "Identity"}, right);
 584              } else if (this._lookahead(0) === TOK_STAR &&
 585                         this._lookahead(1) === TOK_RBRACKET) {
 586                  this._advance();
 587                  this._advance();
 588                  right = this._parseProjectionRHS(bindingPower.Star);
 589                  return {type: "Projection",
 590                          children: [{type: "Identity"}, right]};
 591              } else {
 592                  return this._parseMultiselectList();
 593              }
 594              break;
 595            case TOK_CURRENT:
 596              return {type: TOK_CURRENT};
 597            case TOK_EXPREF:
 598              expression = this.expression(bindingPower.Expref);
 599              return {type: "ExpressionReference", children: [expression]};
 600            case TOK_LPAREN:
 601              var args = [];
 602              while (this._lookahead(0) !== TOK_RPAREN) {
 603                if (this._lookahead(0) === TOK_CURRENT) {
 604                  expression = {type: TOK_CURRENT};
 605                  this._advance();
 606                } else {
 607                  expression = this.expression(0);
 608                }
 609                args.push(expression);
 610              }
 611              this._match(TOK_RPAREN);
 612              return args[0];
 613            default:
 614              this._errorToken(token);
 615          }
 616        },
 617  
 618        led: function(tokenName, left) {
 619          var right;
 620          switch(tokenName) {
 621            case TOK_DOT:
 622              var rbp = bindingPower.Dot;
 623              if (this._lookahead(0) !== TOK_STAR) {
 624                  right = this._parseDotRHS(rbp);
 625                  return {type: "Subexpression", children: [left, right]};
 626              } else {
 627                  // Creating a projection.
 628                  this._advance();
 629                  right = this._parseProjectionRHS(rbp);
 630                  return {type: "ValueProjection", children: [left, right]};
 631              }
 632              break;
 633            case TOK_PIPE:
 634              right = this.expression(bindingPower.Pipe);
 635              return {type: TOK_PIPE, children: [left, right]};
 636            case TOK_OR:
 637              right = this.expression(bindingPower.Or);
 638              return {type: "OrExpression", children: [left, right]};
 639            case TOK_AND:
 640              right = this.expression(bindingPower.And);
 641              return {type: "AndExpression", children: [left, right]};
 642            case TOK_LPAREN:
 643              var name = left.name;
 644              var args = [];
 645              var expression, node;
 646              while (this._lookahead(0) !== TOK_RPAREN) {
 647                if (this._lookahead(0) === TOK_CURRENT) {
 648                  expression = {type: TOK_CURRENT};
 649                  this._advance();
 650                } else {
 651                  expression = this.expression(0);
 652                }
 653                if (this._lookahead(0) === TOK_COMMA) {
 654                  this._match(TOK_COMMA);
 655                }
 656                args.push(expression);
 657              }
 658              this._match(TOK_RPAREN);
 659              node = {type: "Function", name: name, children: args};
 660              return node;
 661            case TOK_FILTER:
 662              var condition = this.expression(0);
 663              this._match(TOK_RBRACKET);
 664              if (this._lookahead(0) === TOK_FLATTEN) {
 665                right = {type: "Identity"};
 666              } else {
 667                right = this._parseProjectionRHS(bindingPower.Filter);
 668              }
 669              return {type: "FilterProjection", children: [left, right, condition]};
 670            case TOK_FLATTEN:
 671              var leftNode = {type: TOK_FLATTEN, children: [left]};
 672              var rightNode = this._parseProjectionRHS(bindingPower.Flatten);
 673              return {type: "Projection", children: [leftNode, rightNode]};
 674            case TOK_EQ:
 675            case TOK_NE:
 676            case TOK_GT:
 677            case TOK_GTE:
 678            case TOK_LT:
 679            case TOK_LTE:
 680              return this._parseComparator(left, tokenName);
 681            case TOK_LBRACKET:
 682              var token = this._lookaheadToken(0);
 683              if (token.type === TOK_NUMBER || token.type === TOK_COLON) {
 684                  right = this._parseIndexExpression();
 685                  return this._projectIfSlice(left, right);
 686              } else {
 687                  this._match(TOK_STAR);
 688                  this._match(TOK_RBRACKET);
 689                  right = this._parseProjectionRHS(bindingPower.Star);
 690                  return {type: "Projection", children: [left, right]};
 691              }
 692              break;
 693            default:
 694              this._errorToken(this._lookaheadToken(0));
 695          }
 696        },
 697  
 698        _match: function(tokenType) {
 699            if (this._lookahead(0) === tokenType) {
 700                this._advance();
 701            } else {
 702                var t = this._lookaheadToken(0);
 703                var error = new Error("Expected " + tokenType + ", got: " + t.type);
 704                error.name = "ParserError";
 705                throw error;
 706            }
 707        },
 708  
 709        _errorToken: function(token) {
 710            var error = new Error("Invalid token (" +
 711                                  token.type + "): \"" +
 712                                  token.value + "\"");
 713            error.name = "ParserError";
 714            throw error;
 715        },
 716  
 717  
 718        _parseIndexExpression: function() {
 719            if (this._lookahead(0) === TOK_COLON || this._lookahead(1) === TOK_COLON) {
 720                return this._parseSliceExpression();
 721            } else {
 722                var node = {
 723                    type: "Index",
 724                    value: this._lookaheadToken(0).value};
 725                this._advance();
 726                this._match(TOK_RBRACKET);
 727                return node;
 728            }
 729        },
 730  
 731        _projectIfSlice: function(left, right) {
 732            var indexExpr = {type: "IndexExpression", children: [left, right]};
 733            if (right.type === "Slice") {
 734                return {
 735                    type: "Projection",
 736                    children: [indexExpr, this._parseProjectionRHS(bindingPower.Star)]
 737                };
 738            } else {
 739                return indexExpr;
 740            }
 741        },
 742  
 743        _parseSliceExpression: function() {
 744            // [start:end:step] where each part is optional, as well as the last
 745            // colon.
 746            var parts = [null, null, null];
 747            var index = 0;
 748            var currentToken = this._lookahead(0);
 749            while (currentToken !== TOK_RBRACKET && index < 3) {
 750                if (currentToken === TOK_COLON) {
 751                    index++;
 752                    this._advance();
 753                } else if (currentToken === TOK_NUMBER) {
 754                    parts[index] = this._lookaheadToken(0).value;
 755                    this._advance();
 756                } else {
 757                    var t = this._lookahead(0);
 758                    var error = new Error("Syntax error, unexpected token: " +
 759                                          t.value + "(" + t.type + ")");
 760                    error.name = "Parsererror";
 761                    throw error;
 762                }
 763                currentToken = this._lookahead(0);
 764            }
 765            this._match(TOK_RBRACKET);
 766            return {
 767                type: "Slice",
 768                children: parts
 769            };
 770        },
 771  
 772        _parseComparator: function(left, comparator) {
 773          var right = this.expression(bindingPower[comparator]);
 774          return {type: "Comparator", name: comparator, children: [left, right]};
 775        },
 776  
 777        _parseDotRHS: function(rbp) {
 778            var lookahead = this._lookahead(0);
 779            var exprTokens = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER, TOK_STAR];
 780            if (exprTokens.indexOf(lookahead) >= 0) {
 781                return this.expression(rbp);
 782            } else if (lookahead === TOK_LBRACKET) {
 783                this._match(TOK_LBRACKET);
 784                return this._parseMultiselectList();
 785            } else if (lookahead === TOK_LBRACE) {
 786                this._match(TOK_LBRACE);
 787                return this._parseMultiselectHash();
 788            }
 789        },
 790  
 791        _parseProjectionRHS: function(rbp) {
 792            var right;
 793            if (bindingPower[this._lookahead(0)] < 10) {
 794                right = {type: "Identity"};
 795            } else if (this._lookahead(0) === TOK_LBRACKET) {
 796                right = this.expression(rbp);
 797            } else if (this._lookahead(0) === TOK_FILTER) {
 798                right = this.expression(rbp);
 799            } else if (this._lookahead(0) === TOK_DOT) {
 800                this._match(TOK_DOT);
 801                right = this._parseDotRHS(rbp);
 802            } else {
 803                var t = this._lookaheadToken(0);
 804                var error = new Error("Sytanx error, unexpected token: " +
 805                                      t.value + "(" + t.type + ")");
 806                error.name = "ParserError";
 807                throw error;
 808            }
 809            return right;
 810        },
 811  
 812        _parseMultiselectList: function() {
 813            var expressions = [];
 814            while (this._lookahead(0) !== TOK_RBRACKET) {
 815                var expression = this.expression(0);
 816                expressions.push(expression);
 817                if (this._lookahead(0) === TOK_COMMA) {
 818                    this._match(TOK_COMMA);
 819                    if (this._lookahead(0) === TOK_RBRACKET) {
 820                      throw new Error("Unexpected token Rbracket");
 821                    }
 822                }
 823            }
 824            this._match(TOK_RBRACKET);
 825            return {type: "MultiSelectList", children: expressions};
 826        },
 827  
 828        _parseMultiselectHash: function() {
 829          var pairs = [];
 830          var identifierTypes = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER];
 831          var keyToken, keyName, value, node;
 832          for (;;) {
 833            keyToken = this._lookaheadToken(0);
 834            if (identifierTypes.indexOf(keyToken.type) < 0) {
 835              throw new Error("Expecting an identifier token, got: " +
 836                              keyToken.type);
 837            }
 838            keyName = keyToken.value;
 839            this._advance();
 840            this._match(TOK_COLON);
 841            value = this.expression(0);
 842            node = {type: "KeyValuePair", name: keyName, value: value};
 843            pairs.push(node);
 844            if (this._lookahead(0) === TOK_COMMA) {
 845              this._match(TOK_COMMA);
 846            } else if (this._lookahead(0) === TOK_RBRACE) {
 847              this._match(TOK_RBRACE);
 848              break;
 849            }
 850          }
 851          return {type: "MultiSelectHash", children: pairs};
 852        }
 853    };
 854  
 855  
 856    function TreeInterpreter(runtime) {
 857      this.runtime = runtime;
 858    }
 859  
 860    TreeInterpreter.prototype = {
 861        search: function(node, value) {
 862            return this.visit(node, value);
 863        },
 864  
 865        visit: function(node, value) {
 866            var matched, current, result, first, second, field, left, right, collected, i;
 867            switch (node.type) {
 868              case "Field":
 869                if (value === null ) {
 870                    return null;
 871                } else if (isObject(value)) {
 872                    field = value[node.name];
 873                    if (field === undefined) {
 874                        return null;
 875                    } else {
 876                        return field;
 877                    }
 878                } else {
 879                  return null;
 880                }
 881                break;
 882              case "Subexpression":
 883                result = this.visit(node.children[0], value);
 884                for (i = 1; i < node.children.length; i++) {
 885                    result = this.visit(node.children[1], result);
 886                    if (result === null) {
 887                        return null;
 888                    }
 889                }
 890                return result;
 891              case "IndexExpression":
 892                left = this.visit(node.children[0], value);
 893                right = this.visit(node.children[1], left);
 894                return right;
 895              case "Index":
 896                if (!isArray(value)) {
 897                  return null;
 898                }
 899                var index = node.value;
 900                if (index < 0) {
 901                  index = value.length + index;
 902                }
 903                result = value[index];
 904                if (result === undefined) {
 905                  result = null;
 906                }
 907                return result;
 908              case "Slice":
 909                if (!isArray(value)) {
 910                  return null;
 911                }
 912                var sliceParams = node.children.slice(0);
 913                var computed = this.computeSliceParams(value.length, sliceParams);
 914                var start = computed[0];
 915                var stop = computed[1];
 916                var step = computed[2];
 917                result = [];
 918                if (step > 0) {
 919                    for (i = start; i < stop; i += step) {
 920                        result.push(value[i]);
 921                    }
 922                } else {
 923                    for (i = start; i > stop; i += step) {
 924                        result.push(value[i]);
 925                    }
 926                }
 927                return result;
 928              case "Projection":
 929                // Evaluate left child.
 930                var base = this.visit(node.children[0], value);
 931                if (!isArray(base)) {
 932                  return null;
 933                }
 934                collected = [];
 935                for (i = 0; i < base.length; i++) {
 936                  current = this.visit(node.children[1], base[i]);
 937                  if (current !== null) {
 938                    collected.push(current);
 939                  }
 940                }
 941                return collected;
 942              case "ValueProjection":
 943                // Evaluate left child.
 944                base = this.visit(node.children[0], value);
 945                if (!isObject(base)) {
 946                  return null;
 947                }
 948                collected = [];
 949                var values = objValues(base);
 950                for (i = 0; i < values.length; i++) {
 951                  current = this.visit(node.children[1], values[i]);
 952                  if (current !== null) {
 953                    collected.push(current);
 954                  }
 955                }
 956                return collected;
 957              case "FilterProjection":
 958                base = this.visit(node.children[0], value);
 959                if (!isArray(base)) {
 960                  return null;
 961                }
 962                var filtered = [];
 963                var finalResults = [];
 964                for (i = 0; i < base.length; i++) {
 965                  matched = this.visit(node.children[2], base[i]);
 966                  if (!isFalse(matched)) {
 967                    filtered.push(base[i]);
 968                  }
 969                }
 970                for (var j = 0; j < filtered.length; j++) {
 971                  current = this.visit(node.children[1], filtered[j]);
 972                  if (current !== null) {
 973                    finalResults.push(current);
 974                  }
 975                }
 976                return finalResults;
 977              case "Comparator":
 978                first = this.visit(node.children[0], value);
 979                second = this.visit(node.children[1], value);
 980                switch(node.name) {
 981                  case TOK_EQ:
 982                    result = strictDeepEqual(first, second);
 983                    break;
 984                  case TOK_NE:
 985                    result = !strictDeepEqual(first, second);
 986                    break;
 987                  case TOK_GT:
 988                    result = first > second;
 989                    break;
 990                  case TOK_GTE:
 991                    result = first >= second;
 992                    break;
 993                  case TOK_LT:
 994                    result = first < second;
 995                    break;
 996                  case TOK_LTE:
 997                    result = first <= second;
 998                    break;
 999                  default:
1000                    throw new Error("Unknown comparator: " + node.name);
1001                }
1002                return result;
1003              case TOK_FLATTEN:
1004                var original = this.visit(node.children[0], value);
1005                if (!isArray(original)) {
1006                  return null;
1007                }
1008                var merged = [];
1009                for (i = 0; i < original.length; i++) {
1010                  current = original[i];
1011                  if (isArray(current)) {
1012                    merged.push.apply(merged, current);
1013                  } else {
1014                    merged.push(current);
1015                  }
1016                }
1017                return merged;
1018              case "Identity":
1019                return value;
1020              case "MultiSelectList":
1021                if (value === null) {
1022                  return null;
1023                }
1024                collected = [];
1025                for (i = 0; i < node.children.length; i++) {
1026                    collected.push(this.visit(node.children[i], value));
1027                }
1028                return collected;
1029              case "MultiSelectHash":
1030                if (value === null) {
1031                  return null;
1032                }
1033                collected = {};
1034                var child;
1035                for (i = 0; i < node.children.length; i++) {
1036                  child = node.children[i];
1037                  collected[child.name] = this.visit(child.value, value);
1038                }
1039                return collected;
1040              case "OrExpression":
1041                matched = this.visit(node.children[0], value);
1042                if (isFalse(matched)) {
1043                    matched = this.visit(node.children[1], value);
1044                }
1045                return matched;
1046              case "AndExpression":
1047                first = this.visit(node.children[0], value);
1048  
1049                if (isFalse(first) === true) {
1050                  return first;
1051                }
1052                return this.visit(node.children[1], value);
1053              case "NotExpression":
1054                first = this.visit(node.children[0], value);
1055                return isFalse(first);
1056              case "Literal":
1057                return node.value;
1058              case TOK_PIPE:
1059                left = this.visit(node.children[0], value);
1060                return this.visit(node.children[1], left);
1061              case TOK_CURRENT:
1062                return value;
1063              case "Function":
1064                var resolvedArgs = [];
1065                for (i = 0; i < node.children.length; i++) {
1066                    resolvedArgs.push(this.visit(node.children[i], value));
1067                }
1068                return this.runtime.callFunction(node.name, resolvedArgs);
1069              case "ExpressionReference":
1070                var refNode = node.children[0];
1071                // Tag the node with a specific attribute so the type
1072                // checker verify the type.
1073                refNode.jmespathType = TOK_EXPREF;
1074                return refNode;
1075              default:
1076                throw new Error("Unknown node type: " + node.type);
1077            }
1078        },
1079  
1080        computeSliceParams: function(arrayLength, sliceParams) {
1081          var start = sliceParams[0];
1082          var stop = sliceParams[1];
1083          var step = sliceParams[2];
1084          var computed = [null, null, null];
1085          if (step === null) {
1086            step = 1;
1087          } else if (step === 0) {
1088            var error = new Error("Invalid slice, step cannot be 0");
1089            error.name = "RuntimeError";
1090            throw error;
1091          }
1092          var stepValueNegative = step < 0 ? true : false;
1093  
1094          if (start === null) {
1095              start = stepValueNegative ? arrayLength - 1 : 0;
1096          } else {
1097              start = this.capSliceRange(arrayLength, start, step);
1098          }
1099  
1100          if (stop === null) {
1101              stop = stepValueNegative ? -1 : arrayLength;
1102          } else {
1103              stop = this.capSliceRange(arrayLength, stop, step);
1104          }
1105          computed[0] = start;
1106          computed[1] = stop;
1107          computed[2] = step;
1108          return computed;
1109        },
1110  
1111        capSliceRange: function(arrayLength, actualValue, step) {
1112            if (actualValue < 0) {
1113                actualValue += arrayLength;
1114                if (actualValue < 0) {
1115                    actualValue = step < 0 ? -1 : 0;
1116                }
1117            } else if (actualValue >= arrayLength) {
1118                actualValue = step < 0 ? arrayLength - 1 : arrayLength;
1119            }
1120            return actualValue;
1121        }
1122  
1123    };
1124  
1125    function Runtime(interpreter) {
1126      this._interpreter = interpreter;
1127      this.functionTable = {
1128          // name: [function, <signature>]
1129          // The <signature> can be:
1130          //
1131          // {
1132          //   args: [[type1, type2], [type1, type2]],
1133          //   variadic: true|false
1134          // }
1135          //
1136          // Each arg in the arg list is a list of valid types
1137          // (if the function is overloaded and supports multiple
1138          // types.  If the type is "any" then no type checking
1139          // occurs on the argument.  Variadic is optional
1140          // and if not provided is assumed to be false.
1141          abs: {_func: this._functionAbs, _signature: [{types: [TYPE_NUMBER]}]},
1142          avg: {_func: this._functionAvg, _signature: [{types: [TYPE_ARRAY_NUMBER]}]},
1143          ceil: {_func: this._functionCeil, _signature: [{types: [TYPE_NUMBER]}]},
1144          contains: {
1145              _func: this._functionContains,
1146              _signature: [{types: [TYPE_STRING, TYPE_ARRAY]},
1147                          {types: [TYPE_ANY]}]},
1148          "ends_with": {
1149              _func: this._functionEndsWith,
1150              _signature: [{types: [TYPE_STRING]}, {types: [TYPE_STRING]}]},
1151          floor: {_func: this._functionFloor, _signature: [{types: [TYPE_NUMBER]}]},
1152          length: {
1153              _func: this._functionLength,
1154              _signature: [{types: [TYPE_STRING, TYPE_ARRAY, TYPE_OBJECT]}]},
1155          map: {
1156              _func: this._functionMap,
1157              _signature: [{types: [TYPE_EXPREF]}, {types: [TYPE_ARRAY]}]},
1158          max: {
1159              _func: this._functionMax,
1160              _signature: [{types: [TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING]}]},
1161          "merge": {
1162              _func: this._functionMerge,
1163              _signature: [{types: [TYPE_OBJECT], variadic: true}]
1164          },
1165          "max_by": {
1166            _func: this._functionMaxBy,
1167            _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}]
1168          },
1169          sum: {_func: this._functionSum, _signature: [{types: [TYPE_ARRAY_NUMBER]}]},
1170          "starts_with": {
1171              _func: this._functionStartsWith,
1172              _signature: [{types: [TYPE_STRING]}, {types: [TYPE_STRING]}]},
1173          min: {
1174              _func: this._functionMin,
1175              _signature: [{types: [TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING]}]},
1176          "min_by": {
1177            _func: this._functionMinBy,
1178            _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}]
1179          },
1180          type: {_func: this._functionType, _signature: [{types: [TYPE_ANY]}]},
1181          keys: {_func: this._functionKeys, _signature: [{types: [TYPE_OBJECT]}]},
1182          values: {_func: this._functionValues, _signature: [{types: [TYPE_OBJECT]}]},
1183          sort: {_func: this._functionSort, _signature: [{types: [TYPE_ARRAY_STRING, TYPE_ARRAY_NUMBER]}]},
1184          "sort_by": {
1185            _func: this._functionSortBy,
1186            _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}]
1187          },
1188          join: {
1189              _func: this._functionJoin,
1190              _signature: [
1191                  {types: [TYPE_STRING]},
1192                  {types: [TYPE_ARRAY_STRING]}
1193              ]
1194          },
1195          reverse: {
1196              _func: this._functionReverse,
1197              _signature: [{types: [TYPE_STRING, TYPE_ARRAY]}]},
1198          "to_array": {_func: this._functionToArray, _signature: [{types: [TYPE_ANY]}]},
1199          "to_string": {_func: this._functionToString, _signature: [{types: [TYPE_ANY]}]},
1200          "to_number": {_func: this._functionToNumber, _signature: [{types: [TYPE_ANY]}]},
1201          "not_null": {
1202              _func: this._functionNotNull,
1203              _signature: [{types: [TYPE_ANY], variadic: true}]
1204          }
1205      };
1206    }
1207  
1208    Runtime.prototype = {
1209      callFunction: function(name, resolvedArgs) {
1210        var functionEntry = this.functionTable[name];
1211        if (functionEntry === undefined) {
1212            throw new Error("Unknown function: " + name + "()");
1213        }
1214        this._validateArgs(name, resolvedArgs, functionEntry._signature);
1215        return functionEntry._func.call(this, resolvedArgs);
1216      },
1217  
1218      _validateArgs: function(name, args, signature) {
1219          // Validating the args requires validating
1220          // the correct arity and the correct type of each arg.
1221          // If the last argument is declared as variadic, then we need
1222          // a minimum number of args to be required.  Otherwise it has to
1223          // be an exact amount.
1224          var pluralized;
1225          if (signature[signature.length - 1].variadic) {
1226              if (args.length < signature.length) {
1227                  pluralized = signature.length === 1 ? " argument" : " arguments";
1228                  throw new Error("ArgumentError: " + name + "() " +
1229                                  "takes at least" + signature.length + pluralized +
1230                                  " but received " + args.length);
1231              }
1232          } else if (args.length !== signature.length) {
1233              pluralized = signature.length === 1 ? " argument" : " arguments";
1234              throw new Error("ArgumentError: " + name + "() " +
1235                              "takes " + signature.length + pluralized +
1236                              " but received " + args.length);
1237          }
1238          var currentSpec;
1239          var actualType;
1240          var typeMatched;
1241          for (var i = 0; i < signature.length; i++) {
1242              typeMatched = false;
1243              currentSpec = signature[i].types;
1244              actualType = this._getTypeName(args[i]);
1245              for (var j = 0; j < currentSpec.length; j++) {
1246                  if (this._typeMatches(actualType, currentSpec[j], args[i])) {
1247                      typeMatched = true;
1248                      break;
1249                  }
1250              }
1251              if (!typeMatched) {
1252                  throw new Error("TypeError: " + name + "() " +
1253                                  "expected argument " + (i + 1) +
1254                                  " to be type " + currentSpec +
1255                                  " but received type " + actualType +
1256                                  " instead.");
1257              }
1258          }
1259      },
1260  
1261      _typeMatches: function(actual, expected, argValue) {
1262          if (expected === TYPE_ANY) {
1263              return true;
1264          }
1265          if (expected === TYPE_ARRAY_STRING ||
1266              expected === TYPE_ARRAY_NUMBER ||
1267              expected === TYPE_ARRAY) {
1268              // The expected type can either just be array,
1269              // or it can require a specific subtype (array of numbers).
1270              //
1271              // The simplest case is if "array" with no subtype is specified.
1272              if (expected === TYPE_ARRAY) {
1273                  return actual === TYPE_ARRAY;
1274              } else if (actual === TYPE_ARRAY) {
1275                  // Otherwise we need to check subtypes.
1276                  // I think this has potential to be improved.
1277                  var subtype;
1278                  if (expected === TYPE_ARRAY_NUMBER) {
1279                    subtype = TYPE_NUMBER;
1280                  } else if (expected === TYPE_ARRAY_STRING) {
1281                    subtype = TYPE_STRING;
1282                  }
1283                  for (var i = 0; i < argValue.length; i++) {
1284                      if (!this._typeMatches(
1285                              this._getTypeName(argValue[i]), subtype,
1286                                               argValue[i])) {
1287                          return false;
1288                      }
1289                  }
1290                  return true;
1291              }
1292          } else {
1293              return actual === expected;
1294          }
1295      },
1296      _getTypeName: function(obj) {
1297          switch (Object.prototype.toString.call(obj)) {
1298              case "[object String]":
1299                return TYPE_STRING;
1300              case "[object Number]":
1301                return TYPE_NUMBER;
1302              case "[object Array]":
1303                return TYPE_ARRAY;
1304              case "[object Boolean]":
1305                return TYPE_BOOLEAN;
1306              case "[object Null]":
1307                return TYPE_NULL;
1308              case "[object Object]":
1309                // Check if it's an expref.  If it has, it's been
1310                // tagged with a jmespathType attr of 'Expref';
1311                if (obj.jmespathType === TOK_EXPREF) {
1312                  return TYPE_EXPREF;
1313                } else {
1314                  return TYPE_OBJECT;
1315                }
1316          }
1317      },
1318  
1319      _functionStartsWith: function(resolvedArgs) {
1320          return resolvedArgs[0].lastIndexOf(resolvedArgs[1]) === 0;
1321      },
1322  
1323      _functionEndsWith: function(resolvedArgs) {
1324          var searchStr = resolvedArgs[0];
1325          var suffix = resolvedArgs[1];
1326          return searchStr.indexOf(suffix, searchStr.length - suffix.length) !== -1;
1327      },
1328  
1329      _functionReverse: function(resolvedArgs) {
1330          var typeName = this._getTypeName(resolvedArgs[0]);
1331          if (typeName === TYPE_STRING) {
1332            var originalStr = resolvedArgs[0];
1333            var reversedStr = "";
1334            for (var i = originalStr.length - 1; i >= 0; i--) {
1335                reversedStr += originalStr[i];
1336            }
1337            return reversedStr;
1338          } else {
1339            var reversedArray = resolvedArgs[0].slice(0);
1340            reversedArray.reverse();
1341            return reversedArray;
1342          }
1343      },
1344  
1345      _functionAbs: function(resolvedArgs) {
1346        return Math.abs(resolvedArgs[0]);
1347      },
1348  
1349      _functionCeil: function(resolvedArgs) {
1350          return Math.ceil(resolvedArgs[0]);
1351      },
1352  
1353      _functionAvg: function(resolvedArgs) {
1354          var sum = 0;
1355          var inputArray = resolvedArgs[0];
1356          for (var i = 0; i < inputArray.length; i++) {
1357              sum += inputArray[i];
1358          }
1359          return sum / inputArray.length;
1360      },
1361  
1362      _functionContains: function(resolvedArgs) {
1363          return resolvedArgs[0].indexOf(resolvedArgs[1]) >= 0;
1364      },
1365  
1366      _functionFloor: function(resolvedArgs) {
1367          return Math.floor(resolvedArgs[0]);
1368      },
1369  
1370      _functionLength: function(resolvedArgs) {
1371         if (!isObject(resolvedArgs[0])) {
1372           return resolvedArgs[0].length;
1373         } else {
1374           // As far as I can tell, there's no way to get the length
1375           // of an object without O(n) iteration through the object.
1376           return Object.keys(resolvedArgs[0]).length;
1377         }
1378      },
1379  
1380      _functionMap: function(resolvedArgs) {
1381        var mapped = [];
1382        var interpreter = this._interpreter;
1383        var exprefNode = resolvedArgs[0];
1384        var elements = resolvedArgs[1];
1385        for (var i = 0; i < elements.length; i++) {
1386            mapped.push(interpreter.visit(exprefNode, elements[i]));
1387        }
1388        return mapped;
1389      },
1390  
1391      _functionMerge: function(resolvedArgs) {
1392        var merged = {};
1393        for (var i = 0; i < resolvedArgs.length; i++) {
1394          var current = resolvedArgs[i];
1395          for (var key in current) {
1396            merged[key] = current[key];
1397          }
1398        }
1399        return merged;
1400      },
1401  
1402      _functionMax: function(resolvedArgs) {
1403        if (resolvedArgs[0].length > 0) {
1404          var typeName = this._getTypeName(resolvedArgs[0][0]);
1405          if (typeName === TYPE_NUMBER) {
1406            return Math.max.apply(Math, resolvedArgs[0]);
1407          } else {
1408            var elements = resolvedArgs[0];
1409            var maxElement = elements[0];
1410            for (var i = 1; i < elements.length; i++) {
1411                if (maxElement.localeCompare(elements[i]) < 0) {
1412                    maxElement = elements[i];
1413                }
1414            }
1415            return maxElement;
1416          }
1417        } else {
1418            return null;
1419        }
1420      },
1421  
1422      _functionMin: function(resolvedArgs) {
1423        if (resolvedArgs[0].length > 0) {
1424          var typeName = this._getTypeName(resolvedArgs[0][0]);
1425          if (typeName === TYPE_NUMBER) {
1426            return Math.min.apply(Math, resolvedArgs[0]);
1427          } else {
1428            var elements = resolvedArgs[0];
1429            var minElement = elements[0];
1430            for (var i = 1; i < elements.length; i++) {
1431                if (elements[i].localeCompare(minElement) < 0) {
1432                    minElement = elements[i];
1433                }
1434            }
1435            return minElement;
1436          }
1437        } else {
1438          return null;
1439        }
1440      },
1441  
1442      _functionSum: function(resolvedArgs) {
1443        var sum = 0;
1444        var listToSum = resolvedArgs[0];
1445        for (var i = 0; i < listToSum.length; i++) {
1446          sum += listToSum[i];
1447        }
1448        return sum;
1449      },
1450  
1451      _functionType: function(resolvedArgs) {
1452          switch (this._getTypeName(resolvedArgs[0])) {
1453            case TYPE_NUMBER:
1454              return "number";
1455            case TYPE_STRING:
1456              return "string";
1457            case TYPE_ARRAY:
1458              return "array";
1459            case TYPE_OBJECT:
1460              return "object";
1461            case TYPE_BOOLEAN:
1462              return "boolean";
1463            case TYPE_EXPREF:
1464              return "expref";
1465            case TYPE_NULL:
1466              return "null";
1467          }
1468      },
1469  
1470      _functionKeys: function(resolvedArgs) {
1471          return Object.keys(resolvedArgs[0]);
1472      },
1473  
1474      _functionValues: function(resolvedArgs) {
1475          var obj = resolvedArgs[0];
1476          var keys = Object.keys(obj);
1477          var values = [];
1478          for (var i = 0; i < keys.length; i++) {
1479              values.push(obj[keys[i]]);
1480          }
1481          return values;
1482      },
1483  
1484      _functionJoin: function(resolvedArgs) {
1485          var joinChar = resolvedArgs[0];
1486          var listJoin = resolvedArgs[1];
1487          return listJoin.join(joinChar);
1488      },
1489  
1490      _functionToArray: function(resolvedArgs) {
1491          if (this._getTypeName(resolvedArgs[0]) === TYPE_ARRAY) {
1492              return resolvedArgs[0];
1493          } else {
1494              return [resolvedArgs[0]];
1495          }
1496      },
1497  
1498      _functionToString: function(resolvedArgs) {
1499          if (this._getTypeName(resolvedArgs[0]) === TYPE_STRING) {
1500              return resolvedArgs[0];
1501          } else {
1502              return JSON.stringify(resolvedArgs[0]);
1503          }
1504      },
1505  
1506      _functionToNumber: function(resolvedArgs) {
1507          var typeName = this._getTypeName(resolvedArgs[0]);
1508          var convertedValue;
1509          if (typeName === TYPE_NUMBER) {
1510              return resolvedArgs[0];
1511          } else if (typeName === TYPE_STRING) {
1512              convertedValue = +resolvedArgs[0];
1513              if (!isNaN(convertedValue)) {
1514                  return convertedValue;
1515              }
1516          }
1517          return null;
1518      },
1519  
1520      _functionNotNull: function(resolvedArgs) {
1521          for (var i = 0; i < resolvedArgs.length; i++) {
1522              if (this._getTypeName(resolvedArgs[i]) !== TYPE_NULL) {
1523                  return resolvedArgs[i];
1524              }
1525          }
1526          return null;
1527      },
1528  
1529      _functionSort: function(resolvedArgs) {
1530          var sortedArray = resolvedArgs[0].slice(0);
1531          sortedArray.sort();
1532          return sortedArray;
1533      },
1534  
1535      _functionSortBy: function(resolvedArgs) {
1536          var sortedArray = resolvedArgs[0].slice(0);
1537          if (sortedArray.length === 0) {
1538              return sortedArray;
1539          }
1540          var interpreter = this._interpreter;
1541          var exprefNode = resolvedArgs[1];
1542          var requiredType = this._getTypeName(
1543              interpreter.visit(exprefNode, sortedArray[0]));
1544          if ([TYPE_NUMBER, TYPE_STRING].indexOf(requiredType) < 0) {
1545              throw new Error("TypeError");
1546          }
1547          var that = this;
1548          // In order to get a stable sort out of an unstable
1549          // sort algorithm, we decorate/sort/undecorate (DSU)
1550          // by creating a new list of [index, element] pairs.
1551          // In the cmp function, if the evaluated elements are
1552          // equal, then the index will be used as the tiebreaker.
1553          // After the decorated list has been sorted, it will be
1554          // undecorated to extract the original elements.
1555          var decorated = [];
1556          for (var i = 0; i < sortedArray.length; i++) {
1557            decorated.push([i, sortedArray[i]]);
1558          }
1559          decorated.sort(function(a, b) {
1560            var exprA = interpreter.visit(exprefNode, a[1]);
1561            var exprB = interpreter.visit(exprefNode, b[1]);
1562            if (that._getTypeName(exprA) !== requiredType) {
1563                throw new Error(
1564                    "TypeError: expected " + requiredType + ", received " +
1565                    that._getTypeName(exprA));
1566            } else if (that._getTypeName(exprB) !== requiredType) {
1567                throw new Error(
1568                    "TypeError: expected " + requiredType + ", received " +
1569                    that._getTypeName(exprB));
1570            }
1571            if (exprA > exprB) {
1572              return 1;
1573            } else if (exprA < exprB) {
1574              return -1;
1575            } else {
1576              // If they're equal compare the items by their
1577              // order to maintain relative order of equal keys
1578              // (i.e. to get a stable sort).
1579              return a[0] - b[0];
1580            }
1581          });
1582          // Undecorate: extract out the original list elements.
1583          for (var j = 0; j < decorated.length; j++) {
1584            sortedArray[j] = decorated[j][1];
1585          }
1586          return sortedArray;
1587      },
1588  
1589      _functionMaxBy: function(resolvedArgs) {
1590        var exprefNode = resolvedArgs[1];
1591        var resolvedArray = resolvedArgs[0];
1592        var keyFunction = this.createKeyFunction(exprefNode, [TYPE_NUMBER, TYPE_STRING]);
1593        var maxNumber = -Infinity;
1594        var maxRecord;
1595        var current;
1596        for (var i = 0; i < resolvedArray.length; i++) {
1597          current = keyFunction(resolvedArray[i]);
1598          if (current > maxNumber) {
1599            maxNumber = current;
1600            maxRecord = resolvedArray[i];
1601          }
1602        }
1603        return maxRecord;
1604      },
1605  
1606      _functionMinBy: function(resolvedArgs) {
1607        var exprefNode = resolvedArgs[1];
1608        var resolvedArray = resolvedArgs[0];
1609        var keyFunction = this.createKeyFunction(exprefNode, [TYPE_NUMBER, TYPE_STRING]);
1610        var minNumber = Infinity;
1611        var minRecord;
1612        var current;
1613        for (var i = 0; i < resolvedArray.length; i++) {
1614          current = keyFunction(resolvedArray[i]);
1615          if (current < minNumber) {
1616            minNumber = current;
1617            minRecord = resolvedArray[i];
1618          }
1619        }
1620        return minRecord;
1621      },
1622  
1623      createKeyFunction: function(exprefNode, allowedTypes) {
1624        var that = this;
1625        var interpreter = this._interpreter;
1626        var keyFunc = function(x) {
1627          var current = interpreter.visit(exprefNode, x);
1628          if (allowedTypes.indexOf(that._getTypeName(current)) < 0) {
1629            var msg = "TypeError: expected one of " + allowedTypes +
1630                      ", received " + that._getTypeName(current);
1631            throw new Error(msg);
1632          }
1633          return current;
1634        };
1635        return keyFunc;
1636      }
1637  
1638    };
1639  
1640    function compile(stream) {
1641      var parser = new Parser();
1642      var ast = parser.parse(stream);
1643      return ast;
1644    }
1645  
1646    function tokenize(stream) {
1647        var lexer = new Lexer();
1648        return lexer.tokenize(stream);
1649    }
1650  
1651    function search(data, expression) {
1652        var parser = new Parser();
1653        // This needs to be improved.  Both the interpreter and runtime depend on
1654        // each other.  The runtime needs the interpreter to support exprefs.
1655        // There's likely a clean way to avoid the cyclic dependency.
1656        var runtime = new Runtime();
1657        var interpreter = new TreeInterpreter(runtime);
1658        runtime._interpreter = interpreter;
1659        var node = parser.parse(expression);
1660        return interpreter.search(node, data);
1661    }
1662  
1663    exports.tokenize = tokenize;
1664    exports.compile = compile;
1665    exports.search = search;
1666    exports.strictDeepEqual = strictDeepEqual;
1667  })(typeof exports === "undefined" ? this.jmespath = {} : exports);