(function(exports)()

in src/aws-client.js [17084:18750]


  (function(exports) {
    "use strict";
  
    function isArray(obj) {
      if (obj !== null) {
        return Object.prototype.toString.call(obj) === "[object Array]";
      } else {
        return false;
      }
    }
  
    function isObject(obj) {
      if (obj !== null) {
        return Object.prototype.toString.call(obj) === "[object Object]";
      } else {
        return false;
      }
    }
  
    function strictDeepEqual(first, second) {
      // Check the scalar case first.
      if (first === second) {
        return true;
      }
  
      // Check if they are the same type.
      var firstType = Object.prototype.toString.call(first);
      if (firstType !== Object.prototype.toString.call(second)) {
        return false;
      }
      // We know that first and second have the same type so we can just check the
      // first type from now on.
      if (isArray(first) === true) {
        // Short circuit if they're not the same length;
        if (first.length !== second.length) {
          return false;
        }
        for (var i = 0; i < first.length; i++) {
          if (strictDeepEqual(first[i], second[i]) === false) {
            return false;
          }
        }
        return true;
      }
      if (isObject(first) === true) {
        // An object is equal if it has the same key/value pairs.
        var keysSeen = {};
        for (var key in first) {
          if (hasOwnProperty.call(first, key)) {
            if (strictDeepEqual(first[key], second[key]) === false) {
              return false;
            }
            keysSeen[key] = true;
          }
        }
        // Now check that there aren't any keys in second that weren't
        // in first.
        for (var key2 in second) {
          if (hasOwnProperty.call(second, key2)) {
            if (keysSeen[key2] !== true) {
              return false;
            }
          }
        }
        return true;
      }
      return false;
    }
  
    function isFalse(obj) {
      // From the spec:
      // A false value corresponds to the following values:
      // Empty list
      // Empty object
      // Empty string
      // False boolean
      // null value
  
      // First check the scalar values.
      if (obj === "" || obj === false || obj === null) {
          return true;
      } else if (isArray(obj) && obj.length === 0) {
          // Check for an empty array.
          return true;
      } else if (isObject(obj)) {
          // Check for an empty object.
          for (var key in obj) {
              // If there are any keys, then
              // the object is not empty so the object
              // is not false.
              if (obj.hasOwnProperty(key)) {
                return false;
              }
          }
          return true;
      } else {
          return false;
      }
    }
  
    function objValues(obj) {
      var keys = Object.keys(obj);
      var values = [];
      for (var i = 0; i < keys.length; i++) {
        values.push(obj[keys[i]]);
      }
      return values;
    }
  
    function merge(a, b) {
        var merged = {};
        for (var key in a) {
            merged[key] = a[key];
        }
        for (var key2 in b) {
            merged[key2] = b[key2];
        }
        return merged;
    }
  
    var trimLeft;
    if (typeof String.prototype.trimLeft === "function") {
      trimLeft = function(str) {
        return str.trimLeft();
      };
    } else {
      trimLeft = function(str) {
        return str.match(/^\s*(.*)/)[1];
      };
    }
  
    // Type constants used to define functions.
    var TYPE_NUMBER = 0;
    var TYPE_ANY = 1;
    var TYPE_STRING = 2;
    var TYPE_ARRAY = 3;
    var TYPE_OBJECT = 4;
    var TYPE_BOOLEAN = 5;
    var TYPE_EXPREF = 6;
    var TYPE_NULL = 7;
    var TYPE_ARRAY_NUMBER = 8;
    var TYPE_ARRAY_STRING = 9;
  
    var TOK_EOF = "EOF";
    var TOK_UNQUOTEDIDENTIFIER = "UnquotedIdentifier";
    var TOK_QUOTEDIDENTIFIER = "QuotedIdentifier";
    var TOK_RBRACKET = "Rbracket";
    var TOK_RPAREN = "Rparen";
    var TOK_COMMA = "Comma";
    var TOK_COLON = "Colon";
    var TOK_RBRACE = "Rbrace";
    var TOK_NUMBER = "Number";
    var TOK_CURRENT = "Current";
    var TOK_EXPREF = "Expref";
    var TOK_PIPE = "Pipe";
    var TOK_OR = "Or";
    var TOK_AND = "And";
    var TOK_EQ = "EQ";
    var TOK_GT = "GT";
    var TOK_LT = "LT";
    var TOK_GTE = "GTE";
    var TOK_LTE = "LTE";
    var TOK_NE = "NE";
    var TOK_FLATTEN = "Flatten";
    var TOK_STAR = "Star";
    var TOK_FILTER = "Filter";
    var TOK_DOT = "Dot";
    var TOK_NOT = "Not";
    var TOK_LBRACE = "Lbrace";
    var TOK_LBRACKET = "Lbracket";
    var TOK_LPAREN= "Lparen";
    var TOK_LITERAL= "Literal";
  
    // The "&", "[", "<", ">" tokens
    // are not in basicToken because
    // there are two token variants
    // ("&&", "[?", "<=", ">=").  This is specially handled
    // below.
  
    var basicTokens = {
      ".": TOK_DOT,
      "*": TOK_STAR,
      ",": TOK_COMMA,
      ":": TOK_COLON,
      "{": TOK_LBRACE,
      "}": TOK_RBRACE,
      "]": TOK_RBRACKET,
      "(": TOK_LPAREN,
      ")": TOK_RPAREN,
      "@": TOK_CURRENT
    };
  
    var operatorStartToken = {
        "<": true,
        ">": true,
        "=": true,
        "!": true
    };
  
    var skipChars = {
        " ": true,
        "\t": true,
        "\n": true
    };
  
  
    function isAlpha(ch) {
        return (ch >= "a" && ch <= "z") ||
               (ch >= "A" && ch <= "Z") ||
               ch === "_";
    }
  
    function isNum(ch) {
        return (ch >= "0" && ch <= "9") ||
               ch === "-";
    }
    function isAlphaNum(ch) {
        return (ch >= "a" && ch <= "z") ||
               (ch >= "A" && ch <= "Z") ||
               (ch >= "0" && ch <= "9") ||
               ch === "_";
    }
  
    function Lexer() {
    }
    Lexer.prototype = {
        tokenize: function(stream) {
            var tokens = [];
            this._current = 0;
            var start;
            var identifier;
            var token;
            while (this._current < stream.length) {
                if (isAlpha(stream[this._current])) {
                    start = this._current;
                    identifier = this._consumeUnquotedIdentifier(stream);
                    tokens.push({type: TOK_UNQUOTEDIDENTIFIER,
                                 value: identifier,
                                 start: start});
                } else if (basicTokens[stream[this._current]] !== undefined) {
                    tokens.push({type: basicTokens[stream[this._current]],
                                value: stream[this._current],
                                start: this._current});
                    this._current++;
                } else if (isNum(stream[this._current])) {
                    token = this._consumeNumber(stream);
                    tokens.push(token);
                } else if (stream[this._current] === "[") {
                    // No need to increment this._current.  This happens
                    // in _consumeLBracket
                    token = this._consumeLBracket(stream);
                    tokens.push(token);
                } else if (stream[this._current] === "\"") {
                    start = this._current;
                    identifier = this._consumeQuotedIdentifier(stream);
                    tokens.push({type: TOK_QUOTEDIDENTIFIER,
                                 value: identifier,
                                 start: start});
                } else if (stream[this._current] === "'") {
                    start = this._current;
                    identifier = this._consumeRawStringLiteral(stream);
                    tokens.push({type: TOK_LITERAL,
                                 value: identifier,
                                 start: start});
                } else if (stream[this._current] === "`") {
                    start = this._current;
                    var literal = this._consumeLiteral(stream);
                    tokens.push({type: TOK_LITERAL,
                                 value: literal,
                                 start: start});
                } else if (operatorStartToken[stream[this._current]] !== undefined) {
                    tokens.push(this._consumeOperator(stream));
                } else if (skipChars[stream[this._current]] !== undefined) {
                    // Ignore whitespace.
                    this._current++;
                } else if (stream[this._current] === "&") {
                    start = this._current;
                    this._current++;
                    if (stream[this._current] === "&") {
                        this._current++;
                        tokens.push({type: TOK_AND, value: "&&", start: start});
                    } else {
                        tokens.push({type: TOK_EXPREF, value: "&", start: start});
                    }
                } else if (stream[this._current] === "|") {
                    start = this._current;
                    this._current++;
                    if (stream[this._current] === "|") {
                        this._current++;
                        tokens.push({type: TOK_OR, value: "||", start: start});
                    } else {
                        tokens.push({type: TOK_PIPE, value: "|", start: start});
                    }
                } else {
                    var error = new Error("Unknown character:" + stream[this._current]);
                    error.name = "LexerError";
                    throw error;
                }
            }
            return tokens;
        },
  
        _consumeUnquotedIdentifier: function(stream) {
            var start = this._current;
            this._current++;
            while (this._current < stream.length && isAlphaNum(stream[this._current])) {
                this._current++;
            }
            return stream.slice(start, this._current);
        },
  
        _consumeQuotedIdentifier: function(stream) {
            var start = this._current;
            this._current++;
            var maxLength = stream.length;
            while (stream[this._current] !== "\"" && this._current < maxLength) {
                // You can escape a double quote and you can escape an escape.
                var current = this._current;
                if (stream[current] === "\\" && (stream[current + 1] === "\\" ||
                                                 stream[current + 1] === "\"")) {
                    current += 2;
                } else {
                    current++;
                }
                this._current = current;
            }
            this._current++;
            return JSON.parse(stream.slice(start, this._current));
        },
  
        _consumeRawStringLiteral: function(stream) {
            var start = this._current;
            this._current++;
            var maxLength = stream.length;
            while (stream[this._current] !== "'" && this._current < maxLength) {
                // You can escape a single quote and you can escape an escape.
                var current = this._current;
                if (stream[current] === "\\" && (stream[current + 1] === "\\" ||
                                                 stream[current + 1] === "'")) {
                    current += 2;
                } else {
                    current++;
                }
                this._current = current;
            }
            this._current++;
            var literal = stream.slice(start + 1, this._current - 1);
            return literal.replace("\\'", "'");
        },
  
        _consumeNumber: function(stream) {
            var start = this._current;
            this._current++;
            var maxLength = stream.length;
            while (isNum(stream[this._current]) && this._current < maxLength) {
                this._current++;
            }
            var value = parseInt(stream.slice(start, this._current));
            return {type: TOK_NUMBER, value: value, start: start};
        },
  
        _consumeLBracket: function(stream) {
            var start = this._current;
            this._current++;
            if (stream[this._current] === "?") {
                this._current++;
                return {type: TOK_FILTER, value: "[?", start: start};
            } else if (stream[this._current] === "]") {
                this._current++;
                return {type: TOK_FLATTEN, value: "[]", start: start};
            } else {
                return {type: TOK_LBRACKET, value: "[", start: start};
            }
        },
  
        _consumeOperator: function(stream) {
            var start = this._current;
            var startingChar = stream[start];
            this._current++;
            if (startingChar === "!") {
                if (stream[this._current] === "=") {
                    this._current++;
                    return {type: TOK_NE, value: "!=", start: start};
                } else {
                  return {type: TOK_NOT, value: "!", start: start};
                }
            } else if (startingChar === "<") {
                if (stream[this._current] === "=") {
                    this._current++;
                    return {type: TOK_LTE, value: "<=", start: start};
                } else {
                    return {type: TOK_LT, value: "<", start: start};
                }
            } else if (startingChar === ">") {
                if (stream[this._current] === "=") {
                    this._current++;
                    return {type: TOK_GTE, value: ">=", start: start};
                } else {
                    return {type: TOK_GT, value: ">", start: start};
                }
            } else if (startingChar === "=") {
                if (stream[this._current] === "=") {
                    this._current++;
                    return {type: TOK_EQ, value: "==", start: start};
                }
            }
        },
  
        _consumeLiteral: function(stream) {
            this._current++;
            var start = this._current;
            var maxLength = stream.length;
            var literal;
            while(stream[this._current] !== "`" && this._current < maxLength) {
                // You can escape a literal char or you can escape the escape.
                var current = this._current;
                if (stream[current] === "\\" && (stream[current + 1] === "\\" ||
                                                 stream[current + 1] === "`")) {
                    current += 2;
                } else {
                    current++;
                }
                this._current = current;
            }
            var literalString = trimLeft(stream.slice(start, this._current));
            literalString = literalString.replace("\\`", "`");
            if (this._looksLikeJSON(literalString)) {
                literal = JSON.parse(literalString);
            } else {
                // Try to JSON parse it as "<literal>"
                literal = JSON.parse("\"" + literalString + "\"");
            }
            // +1 gets us to the ending "`", +1 to move on to the next char.
            this._current++;
            return literal;
        },
  
        _looksLikeJSON: function(literalString) {
            var startingChars = "[{\"";
            var jsonLiterals = ["true", "false", "null"];
            var numberLooking = "-0123456789";
  
            if (literalString === "") {
                return false;
            } else if (startingChars.indexOf(literalString[0]) >= 0) {
                return true;
            } else if (jsonLiterals.indexOf(literalString) >= 0) {
                return true;
            } else if (numberLooking.indexOf(literalString[0]) >= 0) {
                try {
                    JSON.parse(literalString);
                    return true;
                } catch (ex) {
                    return false;
                }
            } else {
                return false;
            }
        }
    };
  
        var bindingPower = {};
        bindingPower[TOK_EOF] = 0;
        bindingPower[TOK_UNQUOTEDIDENTIFIER] = 0;
        bindingPower[TOK_QUOTEDIDENTIFIER] = 0;
        bindingPower[TOK_RBRACKET] = 0;
        bindingPower[TOK_RPAREN] = 0;
        bindingPower[TOK_COMMA] = 0;
        bindingPower[TOK_RBRACE] = 0;
        bindingPower[TOK_NUMBER] = 0;
        bindingPower[TOK_CURRENT] = 0;
        bindingPower[TOK_EXPREF] = 0;
        bindingPower[TOK_PIPE] = 1;
        bindingPower[TOK_OR] = 2;
        bindingPower[TOK_AND] = 3;
        bindingPower[TOK_EQ] = 5;
        bindingPower[TOK_GT] = 5;
        bindingPower[TOK_LT] = 5;
        bindingPower[TOK_GTE] = 5;
        bindingPower[TOK_LTE] = 5;
        bindingPower[TOK_NE] = 5;
        bindingPower[TOK_FLATTEN] = 9;
        bindingPower[TOK_STAR] = 20;
        bindingPower[TOK_FILTER] = 21;
        bindingPower[TOK_DOT] = 40;
        bindingPower[TOK_NOT] = 45;
        bindingPower[TOK_LBRACE] = 50;
        bindingPower[TOK_LBRACKET] = 55;
        bindingPower[TOK_LPAREN] = 60;
  
    function Parser() {
    }
  
    Parser.prototype = {
        parse: function(expression) {
            this._loadTokens(expression);
            this.index = 0;
            var ast = this.expression(0);
            if (this._lookahead(0) !== TOK_EOF) {
                var t = this._lookaheadToken(0);
                var error = new Error(
                    "Unexpected token type: " + t.type + ", value: " + t.value);
                error.name = "ParserError";
                throw error;
            }
            return ast;
        },
  
        _loadTokens: function(expression) {
            var lexer = new Lexer();
            var tokens = lexer.tokenize(expression);
            tokens.push({type: TOK_EOF, value: "", start: expression.length});
            this.tokens = tokens;
        },
  
        expression: function(rbp) {
            var leftToken = this._lookaheadToken(0);
            this._advance();
            var left = this.nud(leftToken);
            var currentToken = this._lookahead(0);
            while (rbp < bindingPower[currentToken]) {
                this._advance();
                left = this.led(currentToken, left);
                currentToken = this._lookahead(0);
            }
            return left;
        },
  
        _lookahead: function(number) {
            return this.tokens[this.index + number].type;
        },
  
        _lookaheadToken: function(number) {
            return this.tokens[this.index + number];
        },
  
        _advance: function() {
            this.index++;
        },
  
        nud: function(token) {
          var left;
          var right;
          var expression;
          switch (token.type) {
            case TOK_LITERAL:
              return {type: "Literal", value: token.value};
            case TOK_UNQUOTEDIDENTIFIER:
              return {type: "Field", name: token.value};
            case TOK_QUOTEDIDENTIFIER:
              var node = {type: "Field", name: token.value};
              if (this._lookahead(0) === TOK_LPAREN) {
                  throw new Error("Quoted identifier not allowed for function names.");
              } else {
                  return node;
              }
              break;
            case TOK_NOT:
              right = this.expression(bindingPower.Not);
              return {type: "NotExpression", children: [right]};
            case TOK_STAR:
              left = {type: "Identity"};
              right = null;
              if (this._lookahead(0) === TOK_RBRACKET) {
                  // This can happen in a multiselect,
                  // [a, b, *]
                  right = {type: "Identity"};
              } else {
                  right = this._parseProjectionRHS(bindingPower.Star);
              }
              return {type: "ValueProjection", children: [left, right]};
            case TOK_FILTER:
              return this.led(token.type, {type: "Identity"});
            case TOK_LBRACE:
              return this._parseMultiselectHash();
            case TOK_FLATTEN:
              left = {type: TOK_FLATTEN, children: [{type: "Identity"}]};
              right = this._parseProjectionRHS(bindingPower.Flatten);
              return {type: "Projection", children: [left, right]};
            case TOK_LBRACKET:
              if (this._lookahead(0) === TOK_NUMBER || this._lookahead(0) === TOK_COLON) {
                  right = this._parseIndexExpression();
                  return this._projectIfSlice({type: "Identity"}, right);
              } else if (this._lookahead(0) === TOK_STAR &&
                         this._lookahead(1) === TOK_RBRACKET) {
                  this._advance();
                  this._advance();
                  right = this._parseProjectionRHS(bindingPower.Star);
                  return {type: "Projection",
                          children: [{type: "Identity"}, right]};
              } else {
                  return this._parseMultiselectList();
              }
              break;
            case TOK_CURRENT:
              return {type: TOK_CURRENT};
            case TOK_EXPREF:
              expression = this.expression(bindingPower.Expref);
              return {type: "ExpressionReference", children: [expression]};
            case TOK_LPAREN:
              var args = [];
              while (this._lookahead(0) !== TOK_RPAREN) {
                if (this._lookahead(0) === TOK_CURRENT) {
                  expression = {type: TOK_CURRENT};
                  this._advance();
                } else {
                  expression = this.expression(0);
                }
                args.push(expression);
              }
              this._match(TOK_RPAREN);
              return args[0];
            default:
              this._errorToken(token);
          }
        },
  
        led: function(tokenName, left) {
          var right;
          switch(tokenName) {
            case TOK_DOT:
              var rbp = bindingPower.Dot;
              if (this._lookahead(0) !== TOK_STAR) {
                  right = this._parseDotRHS(rbp);
                  return {type: "Subexpression", children: [left, right]};
              } else {
                  // Creating a projection.
                  this._advance();
                  right = this._parseProjectionRHS(rbp);
                  return {type: "ValueProjection", children: [left, right]};
              }
              break;
            case TOK_PIPE:
              right = this.expression(bindingPower.Pipe);
              return {type: TOK_PIPE, children: [left, right]};
            case TOK_OR:
              right = this.expression(bindingPower.Or);
              return {type: "OrExpression", children: [left, right]};
            case TOK_AND:
              right = this.expression(bindingPower.And);
              return {type: "AndExpression", children: [left, right]};
            case TOK_LPAREN:
              var name = left.name;
              var args = [];
              var expression, node;
              while (this._lookahead(0) !== TOK_RPAREN) {
                if (this._lookahead(0) === TOK_CURRENT) {
                  expression = {type: TOK_CURRENT};
                  this._advance();
                } else {
                  expression = this.expression(0);
                }
                if (this._lookahead(0) === TOK_COMMA) {
                  this._match(TOK_COMMA);
                }
                args.push(expression);
              }
              this._match(TOK_RPAREN);
              node = {type: "Function", name: name, children: args};
              return node;
            case TOK_FILTER:
              var condition = this.expression(0);
              this._match(TOK_RBRACKET);
              if (this._lookahead(0) === TOK_FLATTEN) {
                right = {type: "Identity"};
              } else {
                right = this._parseProjectionRHS(bindingPower.Filter);
              }
              return {type: "FilterProjection", children: [left, right, condition]};
            case TOK_FLATTEN:
              var leftNode = {type: TOK_FLATTEN, children: [left]};
              var rightNode = this._parseProjectionRHS(bindingPower.Flatten);
              return {type: "Projection", children: [leftNode, rightNode]};
            case TOK_EQ:
            case TOK_NE:
            case TOK_GT:
            case TOK_GTE:
            case TOK_LT:
            case TOK_LTE:
              return this._parseComparator(left, tokenName);
            case TOK_LBRACKET:
              var token = this._lookaheadToken(0);
              if (token.type === TOK_NUMBER || token.type === TOK_COLON) {
                  right = this._parseIndexExpression();
                  return this._projectIfSlice(left, right);
              } else {
                  this._match(TOK_STAR);
                  this._match(TOK_RBRACKET);
                  right = this._parseProjectionRHS(bindingPower.Star);
                  return {type: "Projection", children: [left, right]};
              }
              break;
            default:
              this._errorToken(this._lookaheadToken(0));
          }
        },
  
        _match: function(tokenType) {
            if (this._lookahead(0) === tokenType) {
                this._advance();
            } else {
                var t = this._lookaheadToken(0);
                var error = new Error("Expected " + tokenType + ", got: " + t.type);
                error.name = "ParserError";
                throw error;
            }
        },
  
        _errorToken: function(token) {
            var error = new Error("Invalid token (" +
                                  token.type + "): \"" +
                                  token.value + "\"");
            error.name = "ParserError";
            throw error;
        },
  
  
        _parseIndexExpression: function() {
            if (this._lookahead(0) === TOK_COLON || this._lookahead(1) === TOK_COLON) {
                return this._parseSliceExpression();
            } else {
                var node = {
                    type: "Index",
                    value: this._lookaheadToken(0).value};
                this._advance();
                this._match(TOK_RBRACKET);
                return node;
            }
        },
  
        _projectIfSlice: function(left, right) {
            var indexExpr = {type: "IndexExpression", children: [left, right]};
            if (right.type === "Slice") {
                return {
                    type: "Projection",
                    children: [indexExpr, this._parseProjectionRHS(bindingPower.Star)]
                };
            } else {
                return indexExpr;
            }
        },
  
        _parseSliceExpression: function() {
            // [start:end:step] where each part is optional, as well as the last
            // colon.
            var parts = [null, null, null];
            var index = 0;
            var currentToken = this._lookahead(0);
            while (currentToken !== TOK_RBRACKET && index < 3) {
                if (currentToken === TOK_COLON) {
                    index++;
                    this._advance();
                } else if (currentToken === TOK_NUMBER) {
                    parts[index] = this._lookaheadToken(0).value;
                    this._advance();
                } else {
                    var t = this._lookahead(0);
                    var error = new Error("Syntax error, unexpected token: " +
                                          t.value + "(" + t.type + ")");
                    error.name = "Parsererror";
                    throw error;
                }
                currentToken = this._lookahead(0);
            }
            this._match(TOK_RBRACKET);
            return {
                type: "Slice",
                children: parts
            };
        },
  
        _parseComparator: function(left, comparator) {
          var right = this.expression(bindingPower[comparator]);
          return {type: "Comparator", name: comparator, children: [left, right]};
        },
  
        _parseDotRHS: function(rbp) {
            var lookahead = this._lookahead(0);
            var exprTokens = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER, TOK_STAR];
            if (exprTokens.indexOf(lookahead) >= 0) {
                return this.expression(rbp);
            } else if (lookahead === TOK_LBRACKET) {
                this._match(TOK_LBRACKET);
                return this._parseMultiselectList();
            } else if (lookahead === TOK_LBRACE) {
                this._match(TOK_LBRACE);
                return this._parseMultiselectHash();
            }
        },
  
        _parseProjectionRHS: function(rbp) {
            var right;
            if (bindingPower[this._lookahead(0)] < 10) {
                right = {type: "Identity"};
            } else if (this._lookahead(0) === TOK_LBRACKET) {
                right = this.expression(rbp);
            } else if (this._lookahead(0) === TOK_FILTER) {
                right = this.expression(rbp);
            } else if (this._lookahead(0) === TOK_DOT) {
                this._match(TOK_DOT);
                right = this._parseDotRHS(rbp);
            } else {
                var t = this._lookaheadToken(0);
                var error = new Error("Sytanx error, unexpected token: " +
                                      t.value + "(" + t.type + ")");
                error.name = "ParserError";
                throw error;
            }
            return right;
        },
  
        _parseMultiselectList: function() {
            var expressions = [];
            while (this._lookahead(0) !== TOK_RBRACKET) {
                var expression = this.expression(0);
                expressions.push(expression);
                if (this._lookahead(0) === TOK_COMMA) {
                    this._match(TOK_COMMA);
                    if (this._lookahead(0) === TOK_RBRACKET) {
                      throw new Error("Unexpected token Rbracket");
                    }
                }
            }
            this._match(TOK_RBRACKET);
            return {type: "MultiSelectList", children: expressions};
        },
  
        _parseMultiselectHash: function() {
          var pairs = [];
          var identifierTypes = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER];
          var keyToken, keyName, value, node;
          for (;;) {
            keyToken = this._lookaheadToken(0);
            if (identifierTypes.indexOf(keyToken.type) < 0) {
              throw new Error("Expecting an identifier token, got: " +
                              keyToken.type);
            }
            keyName = keyToken.value;
            this._advance();
            this._match(TOK_COLON);
            value = this.expression(0);
            node = {type: "KeyValuePair", name: keyName, value: value};
            pairs.push(node);
            if (this._lookahead(0) === TOK_COMMA) {
              this._match(TOK_COMMA);
            } else if (this._lookahead(0) === TOK_RBRACE) {
              this._match(TOK_RBRACE);
              break;
            }
          }
          return {type: "MultiSelectHash", children: pairs};
        }
    };
  
  
    function TreeInterpreter(runtime) {
      this.runtime = runtime;
    }
  
    TreeInterpreter.prototype = {
        search: function(node, value) {
            return this.visit(node, value);
        },
  
        visit: function(node, value) {
            var matched, current, result, first, second, field, left, right, collected, i;
            switch (node.type) {
              case "Field":
                if (value === null ) {
                    return null;
                } else if (isObject(value)) {
                    field = value[node.name];
                    if (field === undefined) {
                        return null;
                    } else {
                        return field;
                    }
                } else {
                  return null;
                }
                break;
              case "Subexpression":
                result = this.visit(node.children[0], value);
                for (i = 1; i < node.children.length; i++) {
                    result = this.visit(node.children[1], result);
                    if (result === null) {
                        return null;
                    }
                }
                return result;
              case "IndexExpression":
                left = this.visit(node.children[0], value);
                right = this.visit(node.children[1], left);
                return right;
              case "Index":
                if (!isArray(value)) {
                  return null;
                }
                var index = node.value;
                if (index < 0) {
                  index = value.length + index;
                }
                result = value[index];
                if (result === undefined) {
                  result = null;
                }
                return result;
              case "Slice":
                if (!isArray(value)) {
                  return null;
                }
                var sliceParams = node.children.slice(0);
                var computed = this.computeSliceParams(value.length, sliceParams);
                var start = computed[0];
                var stop = computed[1];
                var step = computed[2];
                result = [];
                if (step > 0) {
                    for (i = start; i < stop; i += step) {
                        result.push(value[i]);
                    }
                } else {
                    for (i = start; i > stop; i += step) {
                        result.push(value[i]);
                    }
                }
                return result;
              case "Projection":
                // Evaluate left child.
                var base = this.visit(node.children[0], value);
                if (!isArray(base)) {
                  return null;
                }
                collected = [];
                for (i = 0; i < base.length; i++) {
                  current = this.visit(node.children[1], base[i]);
                  if (current !== null) {
                    collected.push(current);
                  }
                }
                return collected;
              case "ValueProjection":
                // Evaluate left child.
                base = this.visit(node.children[0], value);
                if (!isObject(base)) {
                  return null;
                }
                collected = [];
                var values = objValues(base);
                for (i = 0; i < values.length; i++) {
                  current = this.visit(node.children[1], values[i]);
                  if (current !== null) {
                    collected.push(current);
                  }
                }
                return collected;
              case "FilterProjection":
                base = this.visit(node.children[0], value);
                if (!isArray(base)) {
                  return null;
                }
                var filtered = [];
                var finalResults = [];
                for (i = 0; i < base.length; i++) {
                  matched = this.visit(node.children[2], base[i]);
                  if (!isFalse(matched)) {
                    filtered.push(base[i]);
                  }
                }
                for (var j = 0; j < filtered.length; j++) {
                  current = this.visit(node.children[1], filtered[j]);
                  if (current !== null) {
                    finalResults.push(current);
                  }
                }
                return finalResults;
              case "Comparator":
                first = this.visit(node.children[0], value);
                second = this.visit(node.children[1], value);
                switch(node.name) {
                  case TOK_EQ:
                    result = strictDeepEqual(first, second);
                    break;
                  case TOK_NE:
                    result = !strictDeepEqual(first, second);
                    break;
                  case TOK_GT:
                    result = first > second;
                    break;
                  case TOK_GTE:
                    result = first >= second;
                    break;
                  case TOK_LT:
                    result = first < second;
                    break;
                  case TOK_LTE:
                    result = first <= second;
                    break;
                  default:
                    throw new Error("Unknown comparator: " + node.name);
                }
                return result;
              case TOK_FLATTEN:
                var original = this.visit(node.children[0], value);
                if (!isArray(original)) {
                  return null;
                }
                var merged = [];
                for (i = 0; i < original.length; i++) {
                  current = original[i];
                  if (isArray(current)) {
                    merged.push.apply(merged, current);
                  } else {
                    merged.push(current);
                  }
                }
                return merged;
              case "Identity":
                return value;
              case "MultiSelectList":
                if (value === null) {
                  return null;
                }
                collected = [];
                for (i = 0; i < node.children.length; i++) {
                    collected.push(this.visit(node.children[i], value));
                }
                return collected;
              case "MultiSelectHash":
                if (value === null) {
                  return null;
                }
                collected = {};
                var child;
                for (i = 0; i < node.children.length; i++) {
                  child = node.children[i];
                  collected[child.name] = this.visit(child.value, value);
                }
                return collected;
              case "OrExpression":
                matched = this.visit(node.children[0], value);
                if (isFalse(matched)) {
                    matched = this.visit(node.children[1], value);
                }
                return matched;
              case "AndExpression":
                first = this.visit(node.children[0], value);
  
                if (isFalse(first) === true) {
                  return first;
                }
                return this.visit(node.children[1], value);
              case "NotExpression":
                first = this.visit(node.children[0], value);
                return isFalse(first);
              case "Literal":
                return node.value;
              case TOK_PIPE:
                left = this.visit(node.children[0], value);
                return this.visit(node.children[1], left);
              case TOK_CURRENT:
                return value;
              case "Function":
                var resolvedArgs = [];
                for (i = 0; i < node.children.length; i++) {
                    resolvedArgs.push(this.visit(node.children[i], value));
                }
                return this.runtime.callFunction(node.name, resolvedArgs);
              case "ExpressionReference":
                var refNode = node.children[0];
                // Tag the node with a specific attribute so the type
                // checker verify the type.
                refNode.jmespathType = TOK_EXPREF;
                return refNode;
              default:
                throw new Error("Unknown node type: " + node.type);
            }
        },
  
        computeSliceParams: function(arrayLength, sliceParams) {
          var start = sliceParams[0];
          var stop = sliceParams[1];
          var step = sliceParams[2];
          var computed = [null, null, null];
          if (step === null) {
            step = 1;
          } else if (step === 0) {
            var error = new Error("Invalid slice, step cannot be 0");
            error.name = "RuntimeError";
            throw error;
          }
          var stepValueNegative = step < 0 ? true : false;
  
          if (start === null) {
              start = stepValueNegative ? arrayLength - 1 : 0;
          } else {
              start = this.capSliceRange(arrayLength, start, step);
          }
  
          if (stop === null) {
              stop = stepValueNegative ? -1 : arrayLength;
          } else {
              stop = this.capSliceRange(arrayLength, stop, step);
          }
          computed[0] = start;
          computed[1] = stop;
          computed[2] = step;
          return computed;
        },
  
        capSliceRange: function(arrayLength, actualValue, step) {
            if (actualValue < 0) {
                actualValue += arrayLength;
                if (actualValue < 0) {
                    actualValue = step < 0 ? -1 : 0;
                }
            } else if (actualValue >= arrayLength) {
                actualValue = step < 0 ? arrayLength - 1 : arrayLength;
            }
            return actualValue;
        }
  
    };
  
    function Runtime(interpreter) {
      this._interpreter = interpreter;
      this.functionTable = {
          // name: [function, <signature>]
          // The <signature> can be:
          //
          // {
          //   args: [[type1, type2], [type1, type2]],
          //   variadic: true|false
          // }
          //
          // Each arg in the arg list is a list of valid types
          // (if the function is overloaded and supports multiple
          // types.  If the type is "any" then no type checking
          // occurs on the argument.  Variadic is optional
          // and if not provided is assumed to be false.
          abs: {_func: this._functionAbs, _signature: [{types: [TYPE_NUMBER]}]},
          avg: {_func: this._functionAvg, _signature: [{types: [TYPE_ARRAY_NUMBER]}]},
          ceil: {_func: this._functionCeil, _signature: [{types: [TYPE_NUMBER]}]},
          contains: {
              _func: this._functionContains,
              _signature: [{types: [TYPE_STRING, TYPE_ARRAY]},
                          {types: [TYPE_ANY]}]},
          "ends_with": {
              _func: this._functionEndsWith,
              _signature: [{types: [TYPE_STRING]}, {types: [TYPE_STRING]}]},
          floor: {_func: this._functionFloor, _signature: [{types: [TYPE_NUMBER]}]},
          length: {
              _func: this._functionLength,
              _signature: [{types: [TYPE_STRING, TYPE_ARRAY, TYPE_OBJECT]}]},
          map: {
              _func: this._functionMap,
              _signature: [{types: [TYPE_EXPREF]}, {types: [TYPE_ARRAY]}]},
          max: {
              _func: this._functionMax,
              _signature: [{types: [TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING]}]},
          "merge": {
              _func: this._functionMerge,
              _signature: [{types: [TYPE_OBJECT], variadic: true}]
          },
          "max_by": {
            _func: this._functionMaxBy,
            _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}]
          },
          sum: {_func: this._functionSum, _signature: [{types: [TYPE_ARRAY_NUMBER]}]},
          "starts_with": {
              _func: this._functionStartsWith,
              _signature: [{types: [TYPE_STRING]}, {types: [TYPE_STRING]}]},
          min: {
              _func: this._functionMin,
              _signature: [{types: [TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING]}]},
          "min_by": {
            _func: this._functionMinBy,
            _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}]
          },
          type: {_func: this._functionType, _signature: [{types: [TYPE_ANY]}]},
          keys: {_func: this._functionKeys, _signature: [{types: [TYPE_OBJECT]}]},
          values: {_func: this._functionValues, _signature: [{types: [TYPE_OBJECT]}]},
          sort: {_func: this._functionSort, _signature: [{types: [TYPE_ARRAY_STRING, TYPE_ARRAY_NUMBER]}]},
          "sort_by": {
            _func: this._functionSortBy,
            _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}]
          },
          join: {
              _func: this._functionJoin,
              _signature: [
                  {types: [TYPE_STRING]},
                  {types: [TYPE_ARRAY_STRING]}
              ]
          },
          reverse: {
              _func: this._functionReverse,
              _signature: [{types: [TYPE_STRING, TYPE_ARRAY]}]},
          "to_array": {_func: this._functionToArray, _signature: [{types: [TYPE_ANY]}]},
          "to_string": {_func: this._functionToString, _signature: [{types: [TYPE_ANY]}]},
          "to_number": {_func: this._functionToNumber, _signature: [{types: [TYPE_ANY]}]},
          "not_null": {
              _func: this._functionNotNull,
              _signature: [{types: [TYPE_ANY], variadic: true}]
          }
      };
    }
  
    Runtime.prototype = {
      callFunction: function(name, resolvedArgs) {
        var functionEntry = this.functionTable[name];
        if (functionEntry === undefined) {
            throw new Error("Unknown function: " + name + "()");
        }
        this._validateArgs(name, resolvedArgs, functionEntry._signature);
        return functionEntry._func.call(this, resolvedArgs);
      },
  
      _validateArgs: function(name, args, signature) {
          // Validating the args requires validating
          // the correct arity and the correct type of each arg.
          // If the last argument is declared as variadic, then we need
          // a minimum number of args to be required.  Otherwise it has to
          // be an exact amount.
          var pluralized;
          if (signature[signature.length - 1].variadic) {
              if (args.length < signature.length) {
                  pluralized = signature.length === 1 ? " argument" : " arguments";
                  throw new Error("ArgumentError: " + name + "() " +
                                  "takes at least" + signature.length + pluralized +
                                  " but received " + args.length);
              }
          } else if (args.length !== signature.length) {
              pluralized = signature.length === 1 ? " argument" : " arguments";
              throw new Error("ArgumentError: " + name + "() " +
                              "takes " + signature.length + pluralized +
                              " but received " + args.length);
          }
          var currentSpec;
          var actualType;
          var typeMatched;
          for (var i = 0; i < signature.length; i++) {
              typeMatched = false;
              currentSpec = signature[i].types;
              actualType = this._getTypeName(args[i]);
              for (var j = 0; j < currentSpec.length; j++) {
                  if (this._typeMatches(actualType, currentSpec[j], args[i])) {
                      typeMatched = true;
                      break;
                  }
              }
              if (!typeMatched) {
                  throw new Error("TypeError: " + name + "() " +
                                  "expected argument " + (i + 1) +
                                  " to be type " + currentSpec +
                                  " but received type " + actualType +
                                  " instead.");
              }
          }
      },
  
      _typeMatches: function(actual, expected, argValue) {
          if (expected === TYPE_ANY) {
              return true;
          }
          if (expected === TYPE_ARRAY_STRING ||
              expected === TYPE_ARRAY_NUMBER ||
              expected === TYPE_ARRAY) {
              // The expected type can either just be array,
              // or it can require a specific subtype (array of numbers).
              //
              // The simplest case is if "array" with no subtype is specified.
              if (expected === TYPE_ARRAY) {
                  return actual === TYPE_ARRAY;
              } else if (actual === TYPE_ARRAY) {
                  // Otherwise we need to check subtypes.
                  // I think this has potential to be improved.
                  var subtype;
                  if (expected === TYPE_ARRAY_NUMBER) {
                    subtype = TYPE_NUMBER;
                  } else if (expected === TYPE_ARRAY_STRING) {
                    subtype = TYPE_STRING;
                  }
                  for (var i = 0; i < argValue.length; i++) {
                      if (!this._typeMatches(
                              this._getTypeName(argValue[i]), subtype,
                                               argValue[i])) {
                          return false;
                      }
                  }
                  return true;
              }
          } else {
              return actual === expected;
          }
      },
      _getTypeName: function(obj) {
          switch (Object.prototype.toString.call(obj)) {
              case "[object String]":
                return TYPE_STRING;
              case "[object Number]":
                return TYPE_NUMBER;
              case "[object Array]":
                return TYPE_ARRAY;
              case "[object Boolean]":
                return TYPE_BOOLEAN;
              case "[object Null]":
                return TYPE_NULL;
              case "[object Object]":
                // Check if it's an expref.  If it has, it's been
                // tagged with a jmespathType attr of 'Expref';
                if (obj.jmespathType === TOK_EXPREF) {
                  return TYPE_EXPREF;
                } else {
                  return TYPE_OBJECT;
                }
          }
      },
  
      _functionStartsWith: function(resolvedArgs) {
          return resolvedArgs[0].lastIndexOf(resolvedArgs[1]) === 0;
      },
  
      _functionEndsWith: function(resolvedArgs) {
          var searchStr = resolvedArgs[0];
          var suffix = resolvedArgs[1];
          return searchStr.indexOf(suffix, searchStr.length - suffix.length) !== -1;
      },
  
      _functionReverse: function(resolvedArgs) {
          var typeName = this._getTypeName(resolvedArgs[0]);
          if (typeName === TYPE_STRING) {
            var originalStr = resolvedArgs[0];
            var reversedStr = "";
            for (var i = originalStr.length - 1; i >= 0; i--) {
                reversedStr += originalStr[i];
            }
            return reversedStr;
          } else {
            var reversedArray = resolvedArgs[0].slice(0);
            reversedArray.reverse();
            return reversedArray;
          }
      },
  
      _functionAbs: function(resolvedArgs) {
        return Math.abs(resolvedArgs[0]);
      },
  
      _functionCeil: function(resolvedArgs) {
          return Math.ceil(resolvedArgs[0]);
      },
  
      _functionAvg: function(resolvedArgs) {
          var sum = 0;
          var inputArray = resolvedArgs[0];
          for (var i = 0; i < inputArray.length; i++) {
              sum += inputArray[i];
          }
          return sum / inputArray.length;
      },
  
      _functionContains: function(resolvedArgs) {
          return resolvedArgs[0].indexOf(resolvedArgs[1]) >= 0;
      },
  
      _functionFloor: function(resolvedArgs) {
          return Math.floor(resolvedArgs[0]);
      },
  
      _functionLength: function(resolvedArgs) {
         if (!isObject(resolvedArgs[0])) {
           return resolvedArgs[0].length;
         } else {
           // As far as I can tell, there's no way to get the length
           // of an object without O(n) iteration through the object.
           return Object.keys(resolvedArgs[0]).length;
         }
      },
  
      _functionMap: function(resolvedArgs) {
        var mapped = [];
        var interpreter = this._interpreter;
        var exprefNode = resolvedArgs[0];
        var elements = resolvedArgs[1];
        for (var i = 0; i < elements.length; i++) {
            mapped.push(interpreter.visit(exprefNode, elements[i]));
        }
        return mapped;
      },
  
      _functionMerge: function(resolvedArgs) {
        var merged = {};
        for (var i = 0; i < resolvedArgs.length; i++) {
          var current = resolvedArgs[i];
          for (var key in current) {
            merged[key] = current[key];
          }
        }
        return merged;
      },
  
      _functionMax: function(resolvedArgs) {
        if (resolvedArgs[0].length > 0) {
          var typeName = this._getTypeName(resolvedArgs[0][0]);
          if (typeName === TYPE_NUMBER) {
            return Math.max.apply(Math, resolvedArgs[0]);
          } else {
            var elements = resolvedArgs[0];
            var maxElement = elements[0];
            for (var i = 1; i < elements.length; i++) {
                if (maxElement.localeCompare(elements[i]) < 0) {
                    maxElement = elements[i];
                }
            }
            return maxElement;
          }
        } else {
            return null;
        }
      },
  
      _functionMin: function(resolvedArgs) {
        if (resolvedArgs[0].length > 0) {
          var typeName = this._getTypeName(resolvedArgs[0][0]);
          if (typeName === TYPE_NUMBER) {
            return Math.min.apply(Math, resolvedArgs[0]);
          } else {
            var elements = resolvedArgs[0];
            var minElement = elements[0];
            for (var i = 1; i < elements.length; i++) {
                if (elements[i].localeCompare(minElement) < 0) {
                    minElement = elements[i];
                }
            }
            return minElement;
          }
        } else {
          return null;
        }
      },
  
      _functionSum: function(resolvedArgs) {
        var sum = 0;
        var listToSum = resolvedArgs[0];
        for (var i = 0; i < listToSum.length; i++) {
          sum += listToSum[i];
        }
        return sum;
      },
  
      _functionType: function(resolvedArgs) {
          switch (this._getTypeName(resolvedArgs[0])) {
            case TYPE_NUMBER:
              return "number";
            case TYPE_STRING:
              return "string";
            case TYPE_ARRAY:
              return "array";
            case TYPE_OBJECT:
              return "object";
            case TYPE_BOOLEAN:
              return "boolean";
            case TYPE_EXPREF:
              return "expref";
            case TYPE_NULL:
              return "null";
          }
      },
  
      _functionKeys: function(resolvedArgs) {
          return Object.keys(resolvedArgs[0]);
      },
  
      _functionValues: function(resolvedArgs) {
          var obj = resolvedArgs[0];
          var keys = Object.keys(obj);
          var values = [];
          for (var i = 0; i < keys.length; i++) {
              values.push(obj[keys[i]]);
          }
          return values;
      },
  
      _functionJoin: function(resolvedArgs) {
          var joinChar = resolvedArgs[0];
          var listJoin = resolvedArgs[1];
          return listJoin.join(joinChar);
      },
  
      _functionToArray: function(resolvedArgs) {
          if (this._getTypeName(resolvedArgs[0]) === TYPE_ARRAY) {
              return resolvedArgs[0];
          } else {
              return [resolvedArgs[0]];
          }
      },
  
      _functionToString: function(resolvedArgs) {
          if (this._getTypeName(resolvedArgs[0]) === TYPE_STRING) {
              return resolvedArgs[0];
          } else {
              return JSON.stringify(resolvedArgs[0]);
          }
      },
  
      _functionToNumber: function(resolvedArgs) {
          var typeName = this._getTypeName(resolvedArgs[0]);
          var convertedValue;
          if (typeName === TYPE_NUMBER) {
              return resolvedArgs[0];
          } else if (typeName === TYPE_STRING) {
              convertedValue = +resolvedArgs[0];
              if (!isNaN(convertedValue)) {
                  return convertedValue;
              }
          }
          return null;
      },
  
      _functionNotNull: function(resolvedArgs) {
          for (var i = 0; i < resolvedArgs.length; i++) {
              if (this._getTypeName(resolvedArgs[i]) !== TYPE_NULL) {
                  return resolvedArgs[i];
              }
          }
          return null;
      },
  
      _functionSort: function(resolvedArgs) {
          var sortedArray = resolvedArgs[0].slice(0);
          sortedArray.sort();
          return sortedArray;
      },
  
      _functionSortBy: function(resolvedArgs) {
          var sortedArray = resolvedArgs[0].slice(0);
          if (sortedArray.length === 0) {
              return sortedArray;
          }
          var interpreter = this._interpreter;
          var exprefNode = resolvedArgs[1];
          var requiredType = this._getTypeName(
              interpreter.visit(exprefNode, sortedArray[0]));
          if ([TYPE_NUMBER, TYPE_STRING].indexOf(requiredType) < 0) {
              throw new Error("TypeError");
          }
          var that = this;
          // In order to get a stable sort out of an unstable
          // sort algorithm, we decorate/sort/undecorate (DSU)
          // by creating a new list of [index, element] pairs.
          // In the cmp function, if the evaluated elements are
          // equal, then the index will be used as the tiebreaker.
          // After the decorated list has been sorted, it will be
          // undecorated to extract the original elements.
          var decorated = [];
          for (var i = 0; i < sortedArray.length; i++) {
            decorated.push([i, sortedArray[i]]);
          }
          decorated.sort(function(a, b) {
            var exprA = interpreter.visit(exprefNode, a[1]);
            var exprB = interpreter.visit(exprefNode, b[1]);
            if (that._getTypeName(exprA) !== requiredType) {
                throw new Error(
                    "TypeError: expected " + requiredType + ", received " +
                    that._getTypeName(exprA));
            } else if (that._getTypeName(exprB) !== requiredType) {
                throw new Error(
                    "TypeError: expected " + requiredType + ", received " +
                    that._getTypeName(exprB));
            }
            if (exprA > exprB) {
              return 1;
            } else if (exprA < exprB) {
              return -1;
            } else {
              // If they're equal compare the items by their
              // order to maintain relative order of equal keys
              // (i.e. to get a stable sort).
              return a[0] - b[0];
            }
          });
          // Undecorate: extract out the original list elements.
          for (var j = 0; j < decorated.length; j++) {
            sortedArray[j] = decorated[j][1];
          }
          return sortedArray;
      },
  
      _functionMaxBy: function(resolvedArgs) {
        var exprefNode = resolvedArgs[1];
        var resolvedArray = resolvedArgs[0];
        var keyFunction = this.createKeyFunction(exprefNode, [TYPE_NUMBER, TYPE_STRING]);
        var maxNumber = -Infinity;
        var maxRecord;
        var current;
        for (var i = 0; i < resolvedArray.length; i++) {
          current = keyFunction(resolvedArray[i]);
          if (current > maxNumber) {
            maxNumber = current;
            maxRecord = resolvedArray[i];
          }
        }
        return maxRecord;
      },
  
      _functionMinBy: function(resolvedArgs) {
        var exprefNode = resolvedArgs[1];
        var resolvedArray = resolvedArgs[0];
        var keyFunction = this.createKeyFunction(exprefNode, [TYPE_NUMBER, TYPE_STRING]);
        var minNumber = Infinity;
        var minRecord;
        var current;
        for (var i = 0; i < resolvedArray.length; i++) {
          current = keyFunction(resolvedArray[i]);
          if (current < minNumber) {
            minNumber = current;
            minRecord = resolvedArray[i];
          }
        }
        return minRecord;
      },
  
      createKeyFunction: function(exprefNode, allowedTypes) {
        var that = this;
        var interpreter = this._interpreter;
        var keyFunc = function(x) {
          var current = interpreter.visit(exprefNode, x);
          if (allowedTypes.indexOf(that._getTypeName(current)) < 0) {
            var msg = "TypeError: expected one of " + allowedTypes +
                      ", received " + that._getTypeName(current);
            throw new Error(msg);
          }
          return current;
        };
        return keyFunc;
      }
  
    };
  
    function compile(stream) {
      var parser = new Parser();
      var ast = parser.parse(stream);
      return ast;
    }
  
    function tokenize(stream) {
        var lexer = new Lexer();
        return lexer.tokenize(stream);
    }
  
    function search(data, expression) {
        var parser = new Parser();
        // This needs to be improved.  Both the interpreter and runtime depend on
        // each other.  The runtime needs the interpreter to support exprefs.
        // There's likely a clean way to avoid the cyclic dependency.
        var runtime = new Runtime();
        var interpreter = new TreeInterpreter(runtime);
        runtime._interpreter = interpreter;
        var node = parser.parse(expression);
        return interpreter.search(node, data);
    }
  
    exports.tokenize = tokenize;
    exports.compile = compile;
    exports.search = search;
    exports.strictDeepEqual = strictDeepEqual;
  })(typeof exports === "undefined" ? this.jmespath = {} : exports);