1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672 |
- (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 TYPE_NAME_TABLE = {
- 0: 'number',
- 1: 'any',
- 2: 'string',
- 3: 'array',
- 4: 'object',
- 5: 'boolean',
- 6: 'expression',
- 7: 'null',
- 8: 'Array<number>',
- 9: 'Array<string>'
- };
- 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.");
- }
- return node;
- 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]};
- }
- return this._parseMultiselectList();
- 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]};
- }
- // Creating a projection.
- this._advance();
- right = this._parseProjectionRHS(rbp);
- return {type: "ValueProjection", children: [left, right]};
- 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);
- }
- this._match(TOK_STAR);
- this._match(TOK_RBRACKET);
- right = this._parseProjectionRHS(bindingPower.Star);
- return {type: "Projection", children: [left, right]};
- 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 && isObject(value)) {
- field = value[node.name];
- if (field === undefined) {
- return null;
- } else {
- return field;
- }
- }
- return null;
- 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) {
- var expected = currentSpec
- .map(function(typeIdentifier) {
- return TYPE_NAME_TABLE[typeIdentifier];
- })
- .join(',');
- throw new Error("TypeError: " + name + "() " +
- "expected argument " + (i + 1) +
- " to be type " + expected +
- " but received type " +
- TYPE_NAME_TABLE[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);
|