|
528 | 528 | },
|
529 | 529 |
|
530 | 530 | nudLbracket: function() {
|
531 |
| - if (this.lookahead(0) === "Number") { |
532 |
| - var node = { |
533 |
| - type: "Index", |
534 |
| - value: this.lookaheadToken(0).value}; |
535 |
| - this.advance(); |
536 |
| - this.match("Rbracket"); |
537 |
| - return node; |
| 531 | + if (this.lookahead(0) === "Number" || this.lookahead(0) === "Colon") { |
| 532 | + return this.parseIndexExpression(); |
538 | 533 | } else if (this.lookahead(0) === "Star" &&
|
539 | 534 | this.lookahead(1) === "Rbracket") {
|
540 | 535 | this.advance();
|
|
547 | 542 | }
|
548 | 543 | },
|
549 | 544 |
|
| 545 | + parseIndexExpression: function() { |
| 546 | + if (this.lookahead(0) === "Colon" || this.lookahead(1) === "Colon") { |
| 547 | + return this.parseSliceExpression(); |
| 548 | + } else { |
| 549 | + var node = { |
| 550 | + type: "Index", |
| 551 | + value: this.lookaheadToken(0).value}; |
| 552 | + this.advance(); |
| 553 | + this.match("Rbracket"); |
| 554 | + return node; |
| 555 | + } |
| 556 | + }, |
| 557 | + |
| 558 | + parseSliceExpression: function() { |
| 559 | + // [start:end:step] where each part is optional, as well as the last |
| 560 | + // colon. |
| 561 | + var parts = [null, null, null]; |
| 562 | + var index = 0; |
| 563 | + var currentToken = this.lookahead(0); |
| 564 | + while (currentToken !== "Rbracket" && index < 3) { |
| 565 | + if (currentToken === "Colon") { |
| 566 | + index++; |
| 567 | + this.advance(); |
| 568 | + } else if (currentToken === "Number") { |
| 569 | + parts[index] = this.lookaheadToken(0).value; |
| 570 | + this.advance(); |
| 571 | + } else { |
| 572 | + var t = this.lookahead(0); |
| 573 | + var error = new Error("Syntax error, unexpected token: " + |
| 574 | + t.value + "(" + t.type + ")"); |
| 575 | + error.name = "Parsererror"; |
| 576 | + throw error; |
| 577 | + } |
| 578 | + currentToken = this.lookahead(0); |
| 579 | + } |
| 580 | + this.match("Rbracket"); |
| 581 | + return { |
| 582 | + type: "Slice", |
| 583 | + children: parts |
| 584 | + }; |
| 585 | + }, |
| 586 | + |
550 | 587 | nudLbrace: function() {
|
551 | 588 | return this.parseMultiselectHash();
|
552 | 589 | },
|
|
613 | 650 | ledLbracket: function(left) {
|
614 | 651 | var token = this.lookaheadToken(0);
|
615 | 652 | var right;
|
616 |
| - if (token.type === "Number") { |
617 |
| - this.match("Number"); |
618 |
| - right = {type: "Index", value: token.value}; |
619 |
| - this.match("Rbracket"); |
| 653 | + if (token.type === "Number" || token.type === "Colon") { |
| 654 | + right = this.parseIndexExpression(); |
620 | 655 | return {type: "IndexExpression", children: [left, right]};
|
621 | 656 | } else {
|
622 | 657 | this.match("Star");
|
|
802 | 837 | return result;
|
803 | 838 | },
|
804 | 839 |
|
| 840 | + visitSlice: function(node, value) { |
| 841 | + if (!isArray(value)) { |
| 842 | + return null; |
| 843 | + } |
| 844 | + var sliceParams = node.children.slice(0); |
| 845 | + var computed = this.computeSliceParams(value.length, sliceParams); |
| 846 | + var start = computed[0]; |
| 847 | + var stop = computed[1]; |
| 848 | + var step = computed[2]; |
| 849 | + var result = []; |
| 850 | + var i; |
| 851 | + if (step > 0) { |
| 852 | + for (i = start; i < stop; i += step) { |
| 853 | + result.push(value[i]); |
| 854 | + } |
| 855 | + } else { |
| 856 | + for (i = start; i > stop; i += step) { |
| 857 | + result.push(value[i]); |
| 858 | + } |
| 859 | + } |
| 860 | + return result; |
| 861 | + }, |
| 862 | + |
| 863 | + computeSliceParams: function(arrayLength, sliceParams) { |
| 864 | + var start = sliceParams[0]; |
| 865 | + var stop = sliceParams[1]; |
| 866 | + var step = sliceParams[2]; |
| 867 | + var computed = [null, null, null]; |
| 868 | + if (step === null) { |
| 869 | + step = 1; |
| 870 | + } else if (step === 0) { |
| 871 | + var error = new Error("Invalid slice, step cannot be 0"); |
| 872 | + error.name = "RuntimeError"; |
| 873 | + throw error; |
| 874 | + } |
| 875 | + var stepValueNegative = step < 0 ? true : false; |
| 876 | + |
| 877 | + if (start === null) { |
| 878 | + start = stepValueNegative ? arrayLength - 1 : 0; |
| 879 | + } else { |
| 880 | + start = this.capSliceRange(arrayLength, start, step); |
| 881 | + } |
| 882 | + |
| 883 | + if (stop === null) { |
| 884 | + stop = stepValueNegative ? -1 : arrayLength; |
| 885 | + } else { |
| 886 | + stop = this.capSliceRange(arrayLength, stop, step); |
| 887 | + } |
| 888 | + computed[0] = start; |
| 889 | + computed[1] = stop; |
| 890 | + computed[2] = step; |
| 891 | + return computed; |
| 892 | + }, |
| 893 | + |
| 894 | + capSliceRange: function(arrayLength, actualValue, step) { |
| 895 | + if (actualValue < 0) { |
| 896 | + actualValue += arrayLength; |
| 897 | + if (actualValue < 0) { |
| 898 | + actualValue = step < 0 ? -1 : 0; |
| 899 | + } |
| 900 | + } else if (actualValue >= arrayLength) { |
| 901 | + actualValue = step < 0 ? arrayLength - 1 : arrayLength; |
| 902 | + } |
| 903 | + return actualValue; |
| 904 | + }, |
| 905 | + |
805 | 906 | visitProjection: function(node, value) {
|
806 | 907 | // Evaluate left child.
|
807 | 908 | var base = this.visit(node.children[0], value);
|
|
992 | 1093 | contains: {
|
993 | 1094 | func: this.functionContains,
|
994 | 1095 | signature: [{types: ["string", "array"]}, {types: ["any"]}]},
|
| 1096 | + "ends_with": { |
| 1097 | + func: this.functionEndsWith, |
| 1098 | + signature: [{types: ["string"]}, {types: ["string"]}]}, |
995 | 1099 | floor: {func: this.functionFloor, signature: [{types: ["number"]}]},
|
996 | 1100 | length: {
|
997 | 1101 | func: this.functionLength,
|
998 | 1102 | signature: [{types: ["string", "array", "object"]}]},
|
999 |
| - max: {func: this.functionMax, signature: [{types: ["array-number"]}]}, |
| 1103 | + max: { |
| 1104 | + func: this.functionMax, |
| 1105 | + signature: [{types: ["array-number", "array-string"]}]}, |
1000 | 1106 | "max_by": {
|
1001 | 1107 | func: this.functionMaxBy,
|
1002 | 1108 | signature: [{types: ["array"]}, {types: ["expref"]}]
|
1003 | 1109 | },
|
1004 | 1110 | sum: {func: this.functionSum, signature: [{types: ["array-number"]}]},
|
1005 |
| - min: {func: this.functionMin, signature: [{types: ["array-number"]}]}, |
| 1111 | + "starts_with": { |
| 1112 | + func: this.functionStartsWith, |
| 1113 | + signature: [{types: ["string"]}, {types: ["string"]}]}, |
| 1114 | + min: { |
| 1115 | + func: this.functionMin, |
| 1116 | + signature: [{types: ["array-number", "array-string"]}]}, |
1006 | 1117 | "min_by": {
|
1007 | 1118 | func: this.functionMinBy,
|
1008 | 1119 | signature: [{types: ["array"]}, {types: ["expref"]}]
|
|
1022 | 1133 | {types: ["array-string"]}
|
1023 | 1134 | ]
|
1024 | 1135 | },
|
| 1136 | + reverse: { |
| 1137 | + func: this.functionReverse, |
| 1138 | + signature: [{types: ["string", "array"]}]}, |
1025 | 1139 | "to_string": {func: this.functionToString, signature: [{types: ["any"]}]},
|
1026 | 1140 | "to_number": {func: this.functionToNumber, signature: [{types: ["any"]}]},
|
1027 | 1141 | "not_null": {
|
|
1135 | 1249 | }
|
1136 | 1250 | },
|
1137 | 1251 |
|
| 1252 | + functionStartsWith: function(resolvedArgs) { |
| 1253 | + return resolvedArgs[0].lastIndexOf(resolvedArgs[1]) === 0; |
| 1254 | + }, |
| 1255 | + |
| 1256 | + functionEndsWith: function(resolvedArgs) { |
| 1257 | + var search = resolvedArgs[0]; |
| 1258 | + var suffix = resolvedArgs[1]; |
| 1259 | + return search.indexOf(suffix, search.length - suffix.length) !== -1; |
| 1260 | + }, |
| 1261 | + |
| 1262 | + functionReverse: function(resolvedArgs) { |
| 1263 | + var typeName = this.getTypeName(resolvedArgs[0]); |
| 1264 | + if (typeName === "string") { |
| 1265 | + var originalStr = resolvedArgs[0]; |
| 1266 | + var reversedStr = ""; |
| 1267 | + for (var i = originalStr.length - 1; i >= 0; i--) { |
| 1268 | + reversedStr += originalStr[i]; |
| 1269 | + } |
| 1270 | + return reversedStr; |
| 1271 | + } else { |
| 1272 | + var reversedArray = resolvedArgs[0].slice(0); |
| 1273 | + reversedArray.reverse(); |
| 1274 | + return reversedArray; |
| 1275 | + } |
| 1276 | + }, |
| 1277 | + |
1138 | 1278 | functionAbs: function(resolvedArgs) {
|
1139 | 1279 | return Math.abs(resolvedArgs[0]);
|
1140 | 1280 | },
|
|
1172 | 1312 |
|
1173 | 1313 | functionMax: function(resolvedArgs) {
|
1174 | 1314 | if (resolvedArgs[0].length > 0) {
|
1175 |
| - return Math.max.apply(Math, resolvedArgs[0]); |
| 1315 | + var typeName = this.getTypeName(resolvedArgs[0][0]); |
| 1316 | + if (typeName === "number") { |
| 1317 | + return Math.max.apply(Math, resolvedArgs[0]); |
| 1318 | + } else { |
| 1319 | + var elements = resolvedArgs[0]; |
| 1320 | + var maxElement = elements[0]; |
| 1321 | + for (var i = 1; i < elements.length; i++) { |
| 1322 | + if (maxElement.localeCompare(elements[i]) < 0) { |
| 1323 | + maxElement = elements[i]; |
| 1324 | + } |
| 1325 | + } |
| 1326 | + return maxElement; |
| 1327 | + } |
1176 | 1328 | } else {
|
1177 | 1329 | return null;
|
1178 | 1330 | }
|
1179 | 1331 | },
|
1180 | 1332 |
|
1181 | 1333 | functionMin: function(resolvedArgs) {
|
1182 | 1334 | if (resolvedArgs[0].length > 0) {
|
1183 |
| - return Math.min.apply(Math, resolvedArgs[0]); |
| 1335 | + var typeName = this.getTypeName(resolvedArgs[0][0]); |
| 1336 | + if (typeName === "number") { |
| 1337 | + return Math.min.apply(Math, resolvedArgs[0]); |
| 1338 | + } else { |
| 1339 | + var elements = resolvedArgs[0]; |
| 1340 | + var minElement = elements[0]; |
| 1341 | + for (var i = 1; i < elements.length; i++) { |
| 1342 | + if (elements[i].localeCompare(minElement) < 0) { |
| 1343 | + minElement = elements[i]; |
| 1344 | + } |
| 1345 | + } |
| 1346 | + return minElement; |
| 1347 | + } |
1184 | 1348 | } else {
|
1185 | 1349 | return null;
|
1186 | 1350 | }
|
|
1313 | 1477 | functionMaxBy: function(resolvedArgs) {
|
1314 | 1478 | var exprefNode = resolvedArgs[1];
|
1315 | 1479 | var resolvedArray = resolvedArgs[0];
|
1316 |
| - var keyFunction = this.createKeyFunction(exprefNode, ["number"]); |
| 1480 | + var keyFunction = this.createKeyFunction(exprefNode, ["number", "string"]); |
1317 | 1481 | var maxNumber = -Infinity;
|
1318 | 1482 | var maxRecord;
|
1319 | 1483 | var current;
|
|
1330 | 1494 | functionMinBy: function(resolvedArgs) {
|
1331 | 1495 | var exprefNode = resolvedArgs[1];
|
1332 | 1496 | var resolvedArray = resolvedArgs[0];
|
1333 |
| - var keyFunction = this.createKeyFunction(exprefNode, ["number"]); |
| 1497 | + var keyFunction = this.createKeyFunction(exprefNode, ["number", "string"]); |
1334 | 1498 | var minNumber = Infinity;
|
1335 | 1499 | var minRecord;
|
1336 | 1500 | var current;
|
|
0 commit comments