From 0ea00b59b0dcd3de003e92a6bc0b27814fd0ec81 Mon Sep 17 00:00:00 2001 From: Jermolene Date: Wed, 6 Feb 2019 14:19:55 +0000 Subject: [PATCH] Add numeric maths filter operators There are other ways we could add maths to TW5 (including @EvanBalster's awesome https://github.com/EvanBalster/TiddlyWikiFormula) but the approach here has the merit of simplicity because it reuses the existing filter evaluation mechanism. That means that it's not ordinary "2+2" maths, it's a unique list processing language... Docs to come Fixes #254 --- core/modules/filters/math.js | 130 +++++++++++++++++++ editions/test/tiddlers/tests/test-filters.js | 14 ++ 2 files changed, 144 insertions(+) create mode 100644 core/modules/filters/math.js diff --git a/core/modules/filters/math.js b/core/modules/filters/math.js new file mode 100644 index 000000000..e28e7fe77 --- /dev/null +++ b/core/modules/filters/math.js @@ -0,0 +1,130 @@ +/*\ +title: $:/core/modules/filters/math.js +type: application/javascript +module-type: filteroperator + +Filter operators for math. Unary/binary operators work on each item in turn, and return a new item list. + +Sum/product/maxall/minall operate on the entire list, returning a single item. + +Note that strings are converted to numbers automatically. Trailing non-digits are ignored. + +* "" converts to 0 +* "12kk" converts to 12 + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +exports.negate = makeNumericBinaryOperator( + function(a) {return -a} +); + +exports.abs = makeNumericBinaryOperator( + function(a) {return Math.abs(a)} +); + +exports.ceil = makeNumericBinaryOperator( + function(a) {return Math.ceil(a)} +); + +exports.floor = makeNumericBinaryOperator( + function(a) {return Math.floor(a)} +); + +exports.round = makeNumericBinaryOperator( + function(a) {return Math.round(a)} +); + +exports.trunc = makeNumericBinaryOperator( + function(a) {return Math.trunc(a)} +); + +exports.sign = makeNumericBinaryOperator( + function(a) {return Math.sign(a)} +); + +exports.add = makeNumericBinaryOperator( + function(a,b) {return a + b;} +); + +exports.subtract = makeNumericBinaryOperator( + function(a,b) {return a - b;} +); + +exports.multiply = makeNumericBinaryOperator( + function(a,b) {return a * b;} +); + +exports.divide = makeNumericBinaryOperator( + function(a,b) {return a / b;} +); + +exports.remainder = makeNumericBinaryOperator( + function(a,b) {return a % b;} +); + +exports.max = makeNumericBinaryOperator( + function(a,b) {return Math.max(a,b);} +); + +exports.min = makeNumericBinaryOperator( + function(a,b) {return Math.min(a,b);} +); + +exports.sum = makeNumericArrayOperator( + function(accumulator,value) {return accumulator + value}, + 0 // Initial value +); + +exports.product = makeNumericArrayOperator( + function(accumulator,value) {return accumulator * value}, + 1 // Initial value +); + +exports.maxall = makeNumericArrayOperator( + function(accumulator,value) {return Math.max(accumulator,value)}, + -Infinity // Initial value +); + +exports.minall = makeNumericArrayOperator( + function(accumulator,value) {return Math.min(accumulator,value)}, + Infinity // Initial value +); + +function makeNumericBinaryOperator(fnCalc) { + return function(source,operator,options) { + var result = [], + numOperand = parseNumber(operator.operand); + source(function(tiddler,title) { + result.push(stringifyNumber(fnCalc(parseNumber(title),numOperand))); + }); + return result; + }; +}; + +function makeNumericArrayOperator(fnCalc,initialValue) { + initialValue = initialValue || 0; + return function(source,operator,options) { + var result = []; + source(function(tiddler,title) { + result.push(title); + }); + return [stringifyNumber(result.reduce(function(accumulator,currentValue) { + return fnCalc(accumulator,parseNumber(currentValue)); + },initialValue))]; + }; +}; + +function parseNumber(str) { + return parseFloat(str) || 0; +} + +function stringifyNumber(num) { + return num + ""; +} + +})(); diff --git a/editions/test/tiddlers/tests/test-filters.js b/editions/test/tiddlers/tests/test-filters.js index 4e75ce04b..7ade538e5 100644 --- a/editions/test/tiddlers/tests/test-filters.js +++ b/editions/test/tiddlers/tests/test-filters.js @@ -361,6 +361,20 @@ describe("Filter tests", function() { expect(wiki.filterTiddlers("[list[TiddlerSeventh]before[MissingTiddler]]").join(",")).toBe("a fourth tiddler"); }); + it("should handle the math operators", function() { + expect(wiki.filterTiddlers("[[2]add[2]]").join(",")).toBe("4"); + expect(wiki.filterTiddlers("[[4]subtract[2]]").join(",")).toBe("2"); + expect(wiki.filterTiddlers("[[24]divide[8]]").join(",")).toBe("3"); + expect(wiki.filterTiddlers("[[355]divide[113]sign[]multiply[4]]").join(",")).toBe("4"); + expect(wiki.filterTiddlers("[[355]divide[113]add[0.5]round[]multiply[4]]").join(",")).toBe("16"); + expect(wiki.filterTiddlers("1 2 3 4 +[sum[]]").join(",")).toBe("10"); + expect(wiki.filterTiddlers("1 2 3 4 +[product[]]").join(",")).toBe("24"); + expect(wiki.filterTiddlers("1 2 3 4 +[maxall[]]").join(",")).toBe("4"); + expect(wiki.filterTiddlers("1 2 3 4 +[minall[]]").join(",")).toBe("1"); + expect(wiki.filterTiddlers("1 2 3 4 +[max[2]]").join(",")).toBe("2,2,3,4"); + expect(wiki.filterTiddlers("1 2 3 4 +[min[2]]").join(",")).toBe("1,2,2,2"); + }); + }); })();