UNPKG

@progress/kendo-ui

Version:

This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.

2,553 lines • 83.6 kB
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};

/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {

/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])
/******/ 			return installedModules[moduleId].exports;

/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			exports: {},
/******/ 			id: moduleId,
/******/ 			loaded: false
/******/ 		};

/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/ 		// Flag the module as loaded
/******/ 		module.loaded = true;

/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}


/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;

/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;

/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";

/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ({

/***/ 0:
/***/ (function(module, exports, __webpack_require__) {

	__webpack_require__(1531);
	module.exports = __webpack_require__(1531);


/***/ }),

/***/ 3:
/***/ (function(module, exports) {

	module.exports = function() { throw new Error("define cannot be used indirect"); };


/***/ }),

/***/ 1485:
/***/ (function(module, exports) {

	module.exports = require("./runtime");

/***/ }),

/***/ 1522:
/***/ (function(module, exports) {

	module.exports = require("../util/main");

/***/ }),

/***/ 1531:
/***/ (function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// -*- fill-column: 100 -*-

	(function(f, define){
	    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(1485), __webpack_require__(1522) ], __WEBPACK_AMD_DEFINE_FACTORY__ = (f), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
	})(function(){
	    "use strict";

	    if (kendo.support.browser.msie && kendo.support.browser.version < 9) {
	        return;
	    }

	    // WARNING: removing the following jshint declaration and turning
	    // == into === to make JSHint happy will break functionality.
	    /* jshint eqnull:true, newcap:false, laxbreak:true, validthis:true */
	    /* jshint latedef:false */

	    var util = kendo.util;
	    var spreadsheet = kendo.spreadsheet;
	    var calc = spreadsheet.calc;
	    var runtime = calc.runtime;
	    var defineFunction = runtime.defineFunction;
	    var defineAlias = runtime.defineAlias;
	    var CalcError = runtime.CalcError;
	    var RangeRef = spreadsheet.RangeRef;
	    var CellRef = spreadsheet.CellRef;
	    var UnionRef = spreadsheet.UnionRef;
	    var Matrix = runtime.Matrix;
	    var Ref = spreadsheet.Ref;
	    var daysInMonth = runtime.daysInMonth;
	    var packDate = runtime.packDate;
	    var unpackDate = runtime.unpackDate;
	    var daysInYear = runtime.daysInYear;

	    /* -----[ Math functions ]----- */

	    [ "abs", "cos", "sin", "acos", "asin", "tan", "atan", "exp", "sqrt" ].forEach(function(name){
	        defineFunction(name, Math[name]).args([
	            [ "*n", "number" ]
	        ]);
	    });

	    defineFunction("ln", Math.log).args([
	        [ "*n", "number" ]
	    ]);

	    defineFunction("log", function(num, base){
	        return Math.log(num) / Math.log(base);
	    }).args([
	        [ "*num", "number++" ],
	        [ "*base", [ "or", "number++", [ "null", 10 ] ] ],
	        [ "?", [ "assert", "$base != 1", "DIV/0" ] ]
	    ]);

	    defineFunction("log10", function(num){
	        return Math.log(num) / Math.log(10);
	    }).args([
	        [ "*num", "number++" ]
	    ]);

	    defineFunction("pi", function(){
	        return Math.PI;
	    }).args([]);

	    defineFunction("sqrtpi", function(n){
	        return Math.sqrt(n * Math.PI);
	    }).args([
	        [ "*num", "number+" ]
	    ]);

	    defineFunction("degrees", function(rad){
	        return ((180 * rad) / Math.PI) % 360;
	    }).args([
	        [ "*radians", "number" ]
	    ]);

	    defineFunction("radians", function(deg){
	        return Math.PI * deg / 180;
	    }).args([
	        [ "*degrees", "number" ]
	    ]);

	    function _cosh(n){
	        return (Math.exp(n) + Math.exp(-n)) / 2;
	    }

	    defineFunction("cosh", _cosh).args([
	        [ "*num", "number" ]
	    ]);

	    defineFunction("acosh", function(n){
	        return Math.log(n + Math.sqrt(n - 1) * Math.sqrt(n + 1));
	    }).args([
	        [ "*num", "number" ],
	        [ "?", [ "assert", "$num >= 1" ] ]
	    ]);

	    function _sinh(n){
	        return (Math.exp(n) - Math.exp(-n)) / 2;
	    }

	    defineFunction("sinh", _sinh).args([
	        [ "*num", "number" ]
	    ]);

	    defineFunction("asinh", function(n){
	        return Math.log(n + Math.sqrt(n * n + 1));
	    }).args([
	        [ "*num", "number" ]
	    ]);

	    defineFunction("sec", function(n){
	        return 1 / Math.cos(n);
	    }).args([
	        [ "*num", "number" ]
	    ]);

	    defineFunction("sech", function(n){
	        return 1 / _cosh(n);
	    }).args([
	        [ "*num", "number" ]
	    ]);

	    defineFunction("csc", function(n){
	        return 1 / Math.sin(n);
	    }).args([
	        [ "*num", "number" ]
	    ]);

	    defineFunction("csch", function(n){
	        return 1 / _sinh(n);
	    }).args([
	        [ "*num", "number" ]
	    ]);

	    defineFunction("atan2", function(x, y){
	        return Math.atan(y / x);
	    }).args([
	        [ "*x", "divisor" ],
	        [ "*y", "number" ]
	    ]);

	    function _tanh(n) {
	        return _sinh(n) / _cosh(n);
	    }

	    defineFunction("tanh", _tanh).args([
	        [ "*num", "number" ]
	    ]);

	    defineFunction("atanh", function(n){
	        return Math.log(Math.sqrt(1 - n*n) / (1 - n));
	    }).args([
	        [ "*num", [ "and", "number", [ "(between)", -1, 1 ] ] ]
	    ]);

	    defineFunction("cot", function(n){
	        return 1 / Math.tan(n);
	    }).args([
	        [ "*num", "divisor" ]
	    ]);

	    defineFunction("coth", function(n){
	        return 1 / _tanh(n);
	    }).args([
	        [ "*num", "divisor" ]
	    ]);

	    defineFunction("acot", function(n){
	        return Math.PI / 2 - Math.atan(n);
	    }).args([
	        [ "*num", "number" ]
	    ]);

	    defineFunction("acoth", function(n){
	        return Math.log((n + 1) / (n - 1)) / 2;
	    }).args([
	        [ "*num", "number" ],
	        [ "?", [ "or",
	                 [ "assert", "$num < -1"],
	                 [ "assert", "$num > 1" ] ] ]
	    ]);

	    defineFunction("power", function(a, b){
	        return Math.pow(a, b);
	    }).args([
	        [ "*a", "number" ],
	        [ "*b", "number" ]
	    ]);

	    defineFunction("mod", function(a, b){
	        return a % b;
	    }).args([
	        [ "*a", "number" ],
	        [ "*b", "divisor" ]
	    ]);

	    defineFunction("quotient", function(a, b){
	        return Math.floor(a / b);
	    }).args([
	        [ "*a", "number" ],
	        [ "*b", "divisor" ]
	    ]);

	    defineFunction("ceiling", function(num, s){
	        return s ? s * Math.ceil(num / s) : 0;
	    }).args([
	        [ "*number", "number" ],
	        [ "*significance", "number" ],
	        [ "?", [ "assert", "$significance >= 0 || $number < 0" ] ]
	    ]);

	    defineFunction("ceiling.precise", function(num, s){
	        s = Math.abs(s);
	        return s ? s * Math.ceil(num / s) : 0;
	    }).args([
	        [ "*number", "number" ],
	        [ "*significance", [ "or", "number", [ "null", 1 ] ] ]
	    ]);

	    defineAlias("iso.ceiling", "ceiling.precise");

	    // XXX: how do we know if this function is correct?
	    //
	    // https://support.office.com/en-gb/article/CEILING-MATH-function-80f95d2f-b499-4eee-9f16-f795a8e306c8
	    //
	    // “There are many combinations of Significance and Mode values that affect rounding of negative
	    // numbers in different ways.” — right, thanks for the info. :-\
	    defineFunction("ceiling.math", function(num, s, mode){
	        if (!s || !num) {
	            return 0;
	        }
	        if (num < 0 && ((!mode && s < 0) || (mode && s > 0))) {
	            s = -s;
	        }
	        return s ? s * Math.ceil(num / s) : 0;
	    }).args([
	        [ "*number", "number" ],
	        [ "*significance", [ "or", "number", [ "null", "$number < 0 ? -1 : 1" ] ] ],
	        [ "*mode", [ "or", "logical", [ "null", 0 ] ] ]
	    ]);

	    defineFunction("floor", function(num, s){
	        return s ? s * Math.floor(num / s) : 0;
	    }).args([
	        [ "*number", "number" ],
	        [ "*significance", "number" ],
	        [ "?", [ "assert", "$significance >= 0 || $number < 0" ] ]
	    ]);

	    defineFunction("floor.precise", function(num, s){
	        s = Math.abs(s);
	        return s ? s * Math.floor(num / s) : 0;
	    }).args([
	        [ "*number", "number" ],
	        [ "*significance", [ "or", "number", [ "null", 1 ] ] ]
	    ]);

	    // XXX: check this
	    defineFunction("floor.math", function(num, s, mode){
	        if (!s || !num) {
	            return 0;
	        }
	        if (num < 0 && ((!mode && s < 0) || (mode && s > 0))) {
	            s = -s;
	        }
	        return s ? s * Math.floor(num / s) : 0;
	    }).args([
	        [ "*number", "number" ],
	        [ "*significance", [ "or", "number", [ "null", "$number < 0 ? -1 : 1" ] ] ],
	        [ "*mode", [ "or", "logical", [ "null", 0 ] ] ]
	    ]);

	    defineFunction("int", Math.floor).args([
	        [ "*number", "number" ]
	    ]);

	    defineFunction("mround", function(num, mult){
	        return mult ? mult * Math.round(num / mult) : 0;
	    }).args([
	        [ "*number", "number" ],
	        [ "*multiple", "number" ]
	    ]);

	    defineFunction("round", function(num, digits){
	        var sign = num < 0 ? -1 : 1;
	        if (sign < 0) { num = -num; }
	        digits = Math.pow(10, digits);
	        num *= digits;
	        num = Math.round(num);
	        return sign * num / digits;
	    }).args([
	        [ "*number", "number" ],
	        [ "*digits", "number" ]
	    ]);

	    defineFunction("roundup", function(num, digits){
	        digits = Math.pow(10, digits);
	        num *= digits;
	        num = num < 0 ? Math.floor(num) : Math.ceil(num);
	        return num / digits;
	    }).args([
	        [ "*number", "number" ],
	        [ "*digits", "number" ]
	    ]);

	    defineFunction("rounddown", function(num, digits){
	        digits = Math.pow(10, digits);
	        num *= digits;
	        num = num < 0 ? Math.ceil(num) : Math.floor(num);
	        return num / digits;
	    }).args([
	        [ "*number", "number" ],
	        [ "*digits", "number" ]
	    ]);

	    defineFunction("even", function(num){
	        var n = num < 0 ? Math.floor(num) : Math.ceil(num);
	        return n % 2 ? n + (n < 0 ? -1 : 1) : n;
	    }).args([
	        [ "*number", "number" ]
	    ]);

	    defineFunction("odd", function(num){
	        var n = num < 0 ? Math.floor(num) : Math.ceil(num);
	        return n % 2 ? n : n + (n < 0 ? -1 : 1);
	    }).args([
	        [ "*number", "number" ]
	    ]);

	    defineFunction("sign", function(num){
	        return num < 0 ? -1 : num > 0 ? 1 : 0;
	    }).args([
	        [ "*number", "number" ]
	    ]);

	    function _gcd(a, b) {
	        while (b) {
	            var r = a % b;
	            a = b;
	            b = r;
	        }
	        return a;
	    }

	    function _lcm(a, b) {
	        return Math.abs(a * b) / _gcd(a, b);
	    }

	    defineFunction("gcd", function(args){
	        var a = args[0];
	        for (var i = 1; i < args.length; ++i) {
	            a = _gcd(a, args[i]);
	        }
	        return a;
	    }).args([
	        [ "numbers", [ "collect", "number" ] ]
	    ]);

	    defineFunction("lcm", function(args){
	        var a = args[0];
	        for (var i = 1; i < args.length; ++i) {
	            a = _lcm(a, args[i]);
	        }
	        return a;
	    }).args([
	        [ "numbers", [ "collect", "number" ] ]
	    ]);

	    defineFunction("sum", function(numbers){
	        return numbers.reduce(function(sum, num){
	            return sum + num;
	        }, 0);
	    }).args([
	        [ "numbers", [ "collect", "number" ] ]
	    ]);

	    defineFunction("product", function(numbers){
	        return numbers.reduce(function(prod, num){
	            return prod * num;
	        }, 1);
	    }).args([
	        [ "numbers", [ "collect", "number" ] ]
	    ]);

	    defineFunction("sumproduct", function(first, rest) {
	        var sum = 0;
	        first.each(function(p, row, col){
	            if (typeof p == "number") {
	                for (var i = 0; i < rest.length; ++i) {
	                    var v = rest[i].get(row, col);
	                    if (typeof v != "number") {
	                        return;
	                    }
	                    p *= v;
	                }
	                sum += p;
	            }
	        });
	        return sum;
	    }).args([
	        [ "a1", "matrix" ],
	        [ "+",
	          [ "a2", [ "and", "matrix",
	                    [ "assert", "$a2.width == $a1.width" ],
	                    [ "assert", "$a2.height == $a1.height" ] ] ] ]
	    ]);

	    defineFunction("sumsq", function(numbers){
	        return numbers.reduce(function(sum, num){
	            return sum + num * num;
	        }, 0);
	    }).args([
	        [ "numbers", [ "collect", "number" ] ]
	    ]);

	    defineFunction("sumx2my2", function(a, b){
	        var sum = 0;
	        a.each(function(x, row, col){
	            var y = b.get(row, col);
	            if (typeof x == "number" && typeof y == "number") {
	                sum += x*x - y*y;
	            }
	        });
	        return sum;
	    }).args([
	        [ "a", "matrix" ],
	        [ "b", [ "and", "matrix",
	                 [ "assert", "$b.width == $a.width" ],
	                 [ "assert", "$b.height == $a.height" ] ] ]
	    ]);

	    defineFunction("sumx2py2", function(a, b){
	        var sum = 0;
	        a.each(function(x, row, col){
	            var y = b.get(row, col);
	            if (typeof x == "number" && typeof y == "number") {
	                sum += x*x + y*y;
	            }
	        });
	        return sum;
	    }).args([
	        [ "a", "matrix" ],
	        [ "b", [ "and", "matrix",
	                 [ "assert", "$b.width == $a.width" ],
	                 [ "assert", "$b.height == $a.height" ] ] ]
	    ]);

	    defineFunction("sumxmy2", function(a, b){
	        var sum = 0;
	        a.each(function(x, row, col){
	            var y = b.get(row, col);
	            if (typeof x == "number" && typeof y == "number") {
	                sum += (x - y) * (x - y);
	            }
	        });
	        return sum;
	    }).args([
	        [ "a", "matrix" ],
	        [ "b", [ "and", "matrix",
	                 [ "assert", "$b.width == $a.width" ],
	                 [ "assert", "$b.height == $a.height" ] ] ]
	    ]);

	    defineFunction("seriessum", function(x, n, m, a){
	        var sum = 0;
	        a.each(function(coef){
	            if (typeof coef != "number") {
	                throw new CalcError("VALUE");
	            }
	            sum += coef * Math.pow(x, n);
	            n += m;
	        });
	        return sum;
	    }).args([
	        [ "x", "number" ],
	        [ "y", "number" ],
	        [ "m", "number" ],
	        [ "a", "matrix" ]
	    ]);

	    defineFunction("min", function(numbers){
	        return numbers.length ? Math.min.apply(Math, numbers) : 0;
	    }).args([
	        [ "numbers", [ "collect", "number" ] ]
	    ]);

	    defineFunction("max", function(numbers){
	        return numbers.length ? Math.max.apply(Math, numbers) : 0;
	    }).args([
	        [ "numbers", [ "collect", "number" ] ]
	    ]);

	    defineFunction("counta", function(values){
	        return values.length;
	    }).args([
	        [ "values", [ "#collect", "anyvalue" ] ]
	    ]);

	    defineFunction("count", function(numbers){
	        return numbers.length;
	    }).args([
	        [ "numbers", [ "#collect", "number" ] ]
	    ]);

	    defineFunction("countunique", function(values){
	        var count = 0, seen = [];
	        values.forEach(function(val){
	            if (seen.indexOf(val) < 0) {
	                count++;
	                seen.push(val);
	            }
	        });
	        return count;
	    }).args([
	        [ "values", [ "#collect", "anyvalue" ] ]
	    ]);

	    defineFunction("countblank", function(a){
	        var count = 0;
	        function add(val) {
	            if (val == null || val === "") {
	                count++;
	            }
	        }
	        function loop(args){
	            for (var i = 0; i < args.length; ++i) {
	                var x = args[i];
	                if (x instanceof Matrix) {
	                    x.each(add, true);
	                } else {
	                    add(x);
	                }
	            }
	        }
	        loop(a);
	        return count;
	    }).args([
	        [ "+", [ "args", [ "or", "matrix", "anyvalue" ] ] ]
	    ]);

	    defineFunction("iseven", function(num){
	        return num % 2 === 0;
	    }).args([
	        [ "*number", "number" ]
	    ]);

	    defineFunction("isodd", function(num){
	        return num % 2 !== 0;
	    }).args([
	        [ "*number", "number" ]
	    ]);

	    defineFunction("n", function(val){
	        if (typeof val == "boolean") {
	            return val ? 1 : 0;
	        }
	        if (typeof val == "number") {
	            return val;
	        }
	        return 0;
	    }).args([
	        [ "*value", "anyvalue" ]
	    ]);

	    defineFunction("na", function(){
	        return new CalcError("N/A");
	    }).args([]);

	    /* -----[ the "*IFS" functions ]----- */

	    // helper function: take `args` like COUNTIFS (see Excel docs) and
	    // calls `f` for each cell matching all criteria.  `f` receives
	    // `chunks` (parsed args containing matrix and predicate) and
	    // row,col of matching cells.
	    function forIFS(args, f) {
	        var chunks = [], i = 0, matrix = args[0];
	        while (i < args.length) {
	            chunks.push({
	                matrix: args[i++],
	                pred: parseCriteria(args[i++])
	            });
	        }
	        ROW: for (var row = 0; row < matrix.height; ++row) {
	            COL: for (var col = 0; col < matrix.width; ++col) {
	                for (i = 0; i < chunks.length; ++i) {
	                    var val = chunks[i].matrix.get(row, col);
	                    if (!chunks[i].pred(val == null || val === "" ? 0 : val)) {
	                        continue COL;
	                    }
	                }
	                f(row, col);
	            }
	        }
	    }

	    var ARGS_COUNTIFS = [
	        [ "m1", "matrix" ],
	        [ "c1", "anyvalue" ],
	        [ [ "m2", "matrix" ],
	          [ "c2", "anyvalue" ] ]
	    ];

	    defineFunction("countifs", function(m1, c1, rest){
	        var count = 0;
	        rest.unshift(m1, c1);
	        forIFS(rest, function(){ count++; });
	        return count;
	    }).args(ARGS_COUNTIFS);

	    var ARGS_SUMIFS = [
	        [ "range", "matrix" ]
	    ].concat(ARGS_COUNTIFS);

	    defineFunction("sumifs", function(range, m1, c1, args){
	        // hack: insert a predicate that filters out non-numeric
	        // values; should also accept blank cells.  it's safe to
	        // modify args.
	        args.unshift(range, numericPredicate, m1, c1);
	        var sum = 0;
	        forIFS(args, function(row, col){
	            var val = range.get(row, col);
	            if (val) {
	                sum += val;
	            }
	        });
	        return sum;
	    }).args(ARGS_SUMIFS);

	    // similar to sumifs, but compute average of matching cells
	    defineFunction("averageifs", function(range, m1, c1, args){
	        args.unshift(range, numericPredicate, m1, c1);
	        var sum = 0, count = 0;
	        forIFS(args, function(row, col){
	            var val = range.get(row, col);
	            if (val == null || val === "") {
	                val = 0;
	            }
	            sum += val;
	            count++;
	        });
	        return count ? sum / count : new CalcError("DIV/0");
	    }).args(ARGS_SUMIFS);

	    defineFunction("countif", function(matrix, criteria){
	        criteria = parseCriteria(criteria);
	        var count = 0;
	        matrix.each(function(val){
	            if (criteria(val)) {
	                count++;
	            }
	        });
	        return count;
	    }).args([
	        [ "range", "matrix" ],
	        [ "*criteria", "anyvalue" ]
	    ]);

	    var ARGS_SUMIF = [
	        [ "range", "matrix" ],
	        [ "*criteria", "anyvalue" ],
	        [ "sumRange", [ "or",
	                        [ "and", "matrix",
	                          [ "assert", "$sumRange.width == $range.width" ],
	                          [ "assert", "$sumRange.height == $range.height" ] ],
	                        [ "null", "$range" ] ] ]
	    ];

	    defineFunction("sumif", function(range, criteria, sumRange){
	        var sum = 0;
	        criteria = parseCriteria(criteria);
	        range.each(function(val, row, col){
	            if (criteria(val)) {
	                var v = sumRange.get(row, col);
	                if (numericPredicate(v)) {
	                    sum += v || 0;
	                }
	            }
	        });
	        return sum;
	    }).args(ARGS_SUMIF);

	    defineFunction("averageif", function(range, criteria, sumRange){
	        var sum = 0, count = 0;
	        criteria = parseCriteria(criteria);
	        range.each(function(val, row, col){
	            if (criteria(val)) {
	                var v = sumRange.get(row, col);
	                if (numericPredicate(v)) {
	                    sum += v || 0;
	                    count++;
	                }
	            }
	        });
	        return count ? sum / count : new CalcError("DIV/0");
	    }).args(ARGS_SUMIF);

	    (function(def){
	        def("large", function(numbers, nth){
	            return numbers.sort(descending)[nth];
	        });
	        def("small", function(numbers, nth){
	            return numbers.sort(ascending)[nth];
	        });
	    })(function(name, handler){
	        defineFunction(name, function(matrix, nth){
	            var numbers = [];
	            var error = matrix.each(function(val){
	                if (val instanceof CalcError) {
	                    return val;
	                }
	                if (typeof val == "number") {
	                    numbers.push(val);
	                }
	            });
	            if (error) {
	                return error;
	            }
	            if (nth > numbers.length) {
	                return new CalcError("NUM");
	            }
	            return handler(numbers, nth - 1);
	        }).args([
	            [ "array", "matrix" ],
	            [ "*nth", "number++" ]
	        ]);
	    });

	    function _avg(numbers) {
	        return numbers.reduce(function(sum, num){
	            return sum + num;
	        }, 0) / numbers.length;
	    }

	    function _var_sp(numbers, divisor, avg) {
	        if (avg == null) {
	            avg = _avg(numbers);
	        }
	        return numbers.reduce(function(sum, num){
	            return sum + Math.pow(num - avg, 2);
	        }, 0) / divisor;
	    }

	    function _stdev_sp(numbers, divisor) {
	        return Math.sqrt(_var_sp(numbers, divisor));
	    }

	    // https://support.office.com/en-sg/article/STDEV-S-function-7d69cf97-0c1f-4acf-be27-f3e83904cc23
	    defineFunction("stdev.s", function(numbers){
	        return _stdev_sp(numbers, numbers.length - 1);
	    }).args([
	        [ "numbers", [ "collect", "number" ] ],
	        [ "?", [ "assert", "$numbers.length >= 2", "NUM" ] ]
	    ]);

	    // https://support.office.com/en-sg/article/STDEV-P-function-6e917c05-31a0-496f-ade7-4f4e7462f285
	    defineFunction("stdev.p", function(numbers){
	        return _stdev_sp(numbers, numbers.length);
	    }).args([
	        [ "numbers", [ "collect", "number" ] ],
	        [ "?", [ "assert", "$numbers.length >= 2", "NUM" ] ]
	    ]);

	    defineFunction("var.s", function(numbers){
	        return _var_sp(numbers, numbers.length - 1);
	    }).args([
	        [ "numbers", [ "collect", "number" ] ],
	        [ "?", [ "assert", "$numbers.length >= 2", "NUM" ] ]
	    ]);

	    defineFunction("var.p", function(numbers){
	        return _var_sp(numbers, numbers.length);
	    }).args([
	        [ "numbers", [ "collect", "number" ] ],
	        [ "?", [ "assert", "$numbers.length >= 2", "NUM" ] ]
	    ]);

	    defineFunction("median", function(numbers){
	        var n = numbers.length;
	        numbers.sort(ascending);
	        if (n % 2) {
	            // when length is odd, the median is the number in the middle
	            return numbers[n >> 1];
	        }
	        // that's the average of the two middle numbers, written in in a fancy way
	        return (numbers[n >>= 1] + numbers[n - 1]) / 2;
	    }).args([
	        [ "numbers", [ "collect", "number" ] ],
	        [ "?", [ "assert", "$numbers.length > 0", "N/A" ] ]
	    ]);

	    defineFunction("mode.sngl", function(numbers){
	        numbers.sort(ascending);
	        var prev = null, count = 0, max = 1, mode = null;
	        for (var i = 0; i < numbers.length; ++i) {
	            var n = numbers[i];
	            if (n != prev) {
	                count = 1;
	                prev = n;
	            } else {
	                count++;
	            }
	            if (count > max) {
	                max = count;
	                mode = n;
	            }
	        }
	        return mode == null ? new CalcError("N/A") : mode;
	    }).args([
	        [ "numbers", [ "collect", "number" ] ]
	    ]);

	    defineFunction("mode.mult", function(numbers){
	        var seen = Object.create(null), max = 2, res = [];
	        numbers.forEach(function(num){
	            var s = seen[num] || 0;
	            seen[num] = ++s;
	            if (s == max) {
	                res.push(num);
	            } else if (s > max) {
	                max = s;
	                res = [ num ];
	            }
	        });
	        var m = new Matrix(this);
	        res.forEach(function(num, i){
	            m.set(i, 0, num);
	        });
	        return m;
	    }).args([
	        [ "numbers", [ "collect", "number" ] ]
	    ]);

	    defineFunction("geomean", function(numbers){
	        var n = numbers.length;
	        var p = numbers.reduce(function(p, num){
	            if (num < 0) {
	                throw new CalcError("NUM");
	            }
	            return p * num;
	        }, 1);
	        return Math.pow(p, 1/n);
	    }).args([
	        [ "numbers", [ "collect", "number" ] ],
	        [ "?", [ "assert", "$numbers.length > 0", "NUM" ] ]
	    ]);

	    defineFunction("harmean", function(numbers){
	        var n = numbers.length;
	        var s = numbers.reduce(function(s, num){
	            if (!num) {
	                throw new CalcError("DIV/0");
	            }
	            return s + 1 / num;
	        }, 0);
	        return n / s;
	    }).args([
	        [ "numbers", [ "collect", "number" ] ],
	        [ "?", [ "assert", "$numbers.length > 0", "NUM" ] ]
	    ]);

	    defineFunction("trimmean", function(numbers, p){
	        var n = numbers.length;
	        numbers.sort(ascending);
	        var discard = Math.floor(n * p);
	        if (discard % 2) {
	            --discard;
	        }
	        discard /= 2;
	        var sum = 0;
	        for (var i = discard; i < n-discard; ++i) {
	            sum += numbers[i];
	        }
	        return sum / (n - discard * 2);
	    }).args([
	        [ "numbers", [ "collect", "number", 1 ] ],
	        [ "percent", [ "and", "number", [ "[between)", 0, 1 ] ] ],
	        [ "?", [ "assert", "$numbers.length > 0", "NUM" ] ]
	    ]);

	    defineFunction("frequency", function(data, bins){
	        // apparently this always returns a vertical matrix in Excel, so we collect all numbers in
	        // bins instead of receiving it as a Matrix and try to mimic its shape.
	        data.sort(ascending);
	        bins.sort(ascending);
	        var prev = -Infinity;
	        var i = 0;
	        function count(max) {
	            var n = 0;
	            while (i < data.length && data[i] > prev && data[i] <= max) {
	                ++n; ++i;
	            }
	            return n;
	        }
	        var m = new Matrix(this);
	        bins.forEach(function(val, i){
	            var n = count(val);
	            prev = val;
	            m.set(i, 0, n);
	        });
	        m.set(m.height, 0, data.length - i);
	        return m;
	    }).args([
	        [ "data", [ "collect", "number", 1 ] ],
	        [ "bins", [ "collect", "number", 1 ] ]
	    ]);

	    defineFunction("rank.eq", function(val, numbers, asc) {
	        numbers.sort(asc ? ascending : descending);
	        var pos = numbers.indexOf(val);
	        return pos < 0 ? new CalcError("N/A") : pos + 1;
	    }).args([
	        [ "value", "number" ],
	        [ "numbers", [ "collect", "number" ] ],
	        [ "order", [ "or", "logical", [ "null", false ] ] ]
	    ]);

	    defineAlias("rank", "rank.eq");

	    defineFunction("rank.avg", function(val, numbers, asc) {
	        numbers.sort(asc ? ascending : descending);
	        var pos = numbers.indexOf(val);
	        if (pos < 0) {
	            return new CalcError("N/A");
	        }
	        for (var i = pos; numbers[i] == val; ++i){}
	        return (pos + i + 1) / 2;
	    }).args([
	        [ "value", "number" ],
	        [ "numbers", [ "collect", "number" ] ],
	        [ "order", [ "or", "logical", [ "null", false ] ] ]
	    ]);

	    // formula available at https://support.office.microsoft.com/en-us/article/KURT-function-cbbc2312-dfa6-4cc4-b5c0-1b3c59cc9377
	    defineFunction("kurt", function(numbers){
	        var n = numbers.length;
	        var avg = _avg(numbers);
	        var variance = _var_sp(numbers, n-1, avg);
	        var stddev = Math.sqrt(variance);
	        var sum = numbers.reduce(function(sum, num){
	            return sum + Math.pow((num - avg) / stddev, 4);
	        }, 0);
	        return n*(n+1)/((n-1)*(n-2)*(n-3)) * sum
	            - 3*Math.pow(n-1, 2)/((n-2)*(n-3));
	    }).args([
	        [ "numbers", [ "collect", "number" ] ],
	        [ "?", [ "assert", "$numbers.length >= 4", "NUM" ] ]
	    ]);

	    function _percentrank(numbers, x, exc) {
	        var nlt = 0, ngt = 0, left = null, right = null, found = false;
	        numbers.forEach(function(num){
	            if (num < x) {
	                nlt++;
	                left = left == null ? num : Math.max(left, num);
	            } else if (num > x) {
	                ngt++;
	                right = right == null ? num : Math.min(right, num);
	            } else {
	                found = true;
	            }
	        });
	        if (!nlt && !ngt) {
	            return new CalcError("N/A");
	        }
	        if (found) {
	            if (exc) {
	                return (nlt + 1) / (numbers.length + 1);
	            }
	            return nlt / (nlt + ngt);
	        }
	        return ((right - x) * _percentrank(numbers, left, exc) +
	                (x - left) * _percentrank(numbers, right, exc)) / (right - left);
	    }

	    var ARGS_PERCENTRANK = [
	        [ "array", [ "collect", "number", 1 ] ],
	        [ "x", "number" ],
	        [ "significance", [ "or", [ "null", 3 ], "integer++" ] ],
	        [ "?", [ "assert", "$array.length > 0", "NUM" ] ]
	    ];

	    defineFunction("percentrank.inc", function(numbers, x, significance) {
	        var p = _percentrank(numbers, x, 0);
	        p = p.toFixed(significance + 1);
	        return parseFloat(p.substr(0, p.length - 1));
	    }).args(ARGS_PERCENTRANK);

	    defineFunction("percentrank.exc", function(numbers, x, significance) {
	        var p = _percentrank(numbers, x, 1);
	        p = p.toFixed(significance + 1);
	        return parseFloat(p.substr(0, p.length - 1));
	    }).args(ARGS_PERCENTRANK);

	    defineAlias("percentrank", "percentrank.inc");

	    function _covariance(x, y, divisor) {
	        var sum = 0;
	        var ax = _avg(x);
	        var ay = _avg(y);
	        var n = x.length;
	        for (var i = 0; i < n; ++i) {
	            sum += (x[i] - ax) * (y[i] - ay);
	        }
	        return sum / divisor;
	    }

	    defineFunction("covariance.p", function(x, y){
	        return _covariance(x, y, x.length);
	    }).args([
	        [ "array1", [ "collect", "number", 1 ] ],
	        [ "array2", [ "collect", "number", 1 ] ],
	        [ "?", [ "assert", "$array1.length == $array2.length", "N/A" ] ],
	        [ "?", [ "assert", "$array1.length > 0", "DIV/0" ] ]
	    ]);

	    defineFunction("covariance.s", function(x, y){
	        return _covariance(x, y, x.length - 1);
	    }).args([
	        [ "array1", [ "collect", "number", 1 ] ],
	        [ "array2", [ "collect", "number", 1 ] ],
	        [ "?", [ "assert", "$array1.length == $array2.length", "N/A" ] ],
	        [ "?", [ "assert", "$array1.length > 1", "DIV/0" ] ]
	    ]);

	    defineAlias("covar", "covariance.p");

	    /* -----[ Factorials ]----- */

	    var _fact = util.memoize(function(n){
	        for (var i = 2, fact = 1; i <= n; ++i) {
	            fact *= i;
	        }
	        return fact;
	    });

	    defineFunction("fact", _fact).args([
	        [ "*n", "integer+" ]
	    ]);

	    defineFunction("factdouble", function(n){
	        for (var i = 2 + (n&1), fact = 1; i <= n; i += 2) {
	            fact *= i;
	        }
	        return fact;
	    }).args([
	        [ "*n", "integer+" ]
	    ]);

	    defineFunction("multinomial", function(numbers){
	        var div = 1, sum = 0;
	        numbers.forEach(function(n){
	            if (n < 0) {
	                throw new CalcError("NUM");
	            }
	            sum += n;
	            div *= _fact(n);
	        });
	        return _fact(sum) / div;
	    }).args([
	        [ "numbers", [ "collect", "number" ] ]
	    ]);

	    var _combinations = util.memoize(function (n, k){
	        for (var f1 = k + 1, f2 = 1, p1 = 1, p2 = 1; f2 <= n - k; ++f1, ++f2) {
	            p1 *= f1;
	            p2 *= f2;
	        }
	        return p1/p2;
	    });

	    defineFunction("combin", _combinations).args([
	        [ "*n", "integer++" ],
	        [ "*k", [ "and", "integer", [ "[between]", 0, "$n" ] ] ]
	    ]);

	    defineFunction("combina", function(n, k){
	        return _combinations(n + k - 1, n - 1);
	    }).args([
	        [ "*n", "integer++" ],
	        [ "*k", [ "and", "integer", [ "[between]", 1, "$n" ] ] ]
	    ]);

	    /* -----[ Statistical functions ]----- */

	    defineFunction("average", function(numbers){
	        var sum = numbers.reduce(function(sum, num){
	            return sum + num;
	        }, 0);
	        return sum / numbers.length;
	    }).args([
	        // most numeric functions must treat booleans as numbers (1 for TRUE
	        // and 0 for FALSE), but AVERAGE shouldn't.
	        [ "numbers", [ "collect", "number!" ] ],
	        [ "?", [ "assert", "$numbers.length > 0", "DIV/0" ] ]
	    ]);

	    defineFunction("averagea", function(values){
	        var sum = 0, count = 0;
	        values.forEach(function(num){
	            if (typeof num != "string") {
	                sum += num;
	            }
	            ++count;
	        });
	        return count ? sum / count : new CalcError("DIV/0");
	    }).args([
	        [ "values", [ "collect", "anyvalue" ] ]
	    ]);

	    function _percentile(numbers, rank) {
	        numbers.sort(ascending);
	        var n = numbers.length;
	        var k = rank | 0, d = rank - k;
	        if (k === 0) {
	            return numbers[0];
	        }
	        if (k >= n) {
	            return numbers[n - 1];
	        }
	        --k;
	        return numbers[k] + d * (numbers[k + 1] - numbers[k]);
	    }

	    function _percentile_inc(numbers, p){
	        // algorithm from https://en.wikipedia.org/wiki/Percentile#Microsoft_Excel_method
	        var rank = p * (numbers.length - 1) + 1;
	        return _percentile(numbers, rank);
	    }

	    function _percentile_exc(numbers, p){
	        // https://en.wikipedia.org/wiki/Percentile#NIST_method
	        var rank = p * (numbers.length + 1);
	        return _percentile(numbers, rank);
	    }

	    defineFunction("percentile.inc", _percentile_inc).args([
	        [ "numbers", [ "collect", "number", 1 ] ],
	        [ "p", [ "and", "number", [ "[between]", 0, 1 ] ] ]
	    ]);

	    defineFunction("percentile.exc", _percentile_exc).args([
	        [ "numbers", [ "collect", "number", 1 ] ],
	        [ "p", [ "and", "number", [ "(between)", 0, 1 ] ] ]
	    ]);

	    defineFunction("quartile.inc", function(numbers, quarter){
	        return _percentile_inc(numbers, quarter / 4);
	    }).args([
	        [ "numbers", [ "collect", "number", 1 ] ],
	        [ "quarter", [ "values", 0, 1, 2, 3, 4 ] ]
	    ]);

	    defineFunction("quartile.exc", function(numbers, quarter){
	        return _percentile_exc(numbers, quarter / 4);
	    }).args([
	        [ "numbers", [ "collect", "number", 1 ] ],
	        [ "quarter", [ "values", 0, 1, 2, 3, 4 ] ]
	    ]);

	    defineAlias("quartile", "quartile.inc");
	    defineAlias("percentile", "percentile.inc");

	    var AGGREGATE_FUNCS = [
	        "AVERAGE", "COUNT", "COUNTA", "MAX", "MIN", "PRODUCT",
	        "STDEV.S", "STDEV.P", "SUM", "VAR.S", "VAR.P", "MEDIAN",
	        "MODE.SNGL", "LARGE", "SMALL", "PERCENTILE.INC",
	        "QUARTILE.INC", "PERCENTILE.EXC", "QUARTILE.EXC"
	    ];

	    function fetchValuesForAggregate(self, args, options) {
	        var values = [];
	        var opt_ignore_hidden_rows = 1;
	        var opt_ignore_errors = 2;
	        var opt_use_aggregates = 4;
	        (function fetchValues(args) {
	            if (args instanceof Ref) {
	                self.getRefCells(args, true).forEach(function(cell){
	                    var value = cell.value;
	                    if ((options & opt_ignore_hidden_rows) && cell.hidden) {
	                        return;
	                    }
	                    if (cell.formula) {
	                        // XXX: formula.print is fast, but still, can't we do any better here?
	                        //      perhaps access the input string directly somehow?
	                        var str = cell.formula.print(cell.row, cell.col);
	                        if (/^\s*(?:aggregate|subtotal)\s*\(/i.test(str)) {
	                            if (!(options & opt_use_aggregates)) {
	                                return;
	                            }
	                        }
	                        if ("value" in cell.formula) {
	                            value = cell.formula.value;
	                        }
	                    }
	                    if ((options & opt_ignore_errors) && value instanceof CalcError) {
	                        return;
	                    }
	                    if (typeof value == "number" || value instanceof CalcError) {
	                        values.push(value);
	                    }
	                });
	            } else if (Array.isArray(args)) {
	                for (var i = 0; i < args.length; ++i) {
	                    fetchValues(args[i]);
	                }
	            } else if (args instanceof Matrix) {
	                args.each(fetchValues);
	            } else if (typeof args == "number") {
	                values.push(args);
	            } else if (args instanceof CalcError && !(options & opt_ignore_errors)) {
	                values.push(args);
	            }
	        })(args);
	        return values;
	    }

	    // AGGREGATE function
	    //
	    // https://support.office.com/en-SG/article/aggregate-function-c8caed56-07df-4aeb-9741-23693ffbe525
	    //
	    // we can only partially type-check this function.  also, we need to use the async version in
	    // order to resolve references and delegate values to the function to aggregate.
	    defineFunction("aggregate", function(callback, funcId, options, args){
	        // options is a bit field.  that makes sense; it's the documentation which doesn't.
	        var self = this;
	        self.resolveCells(args, function(){
	            var values;
	            if (funcId > 12) {
	                // "array form"
	                values = fetchValuesForAggregate(self, args[0], options);
	                var k = args[1];
	                if (k instanceof CellRef) {
	                    k = self.getRefData(k);
	                }
	                if (typeof k != "number") {
	                    return callback(new CalcError("VALUE"));
	                }
	            } else {
	                values = fetchValuesForAggregate(self, args, options);
	            }
	            self.func(AGGREGATE_FUNCS[funcId - 1], callback, values);
	        });
	    }).argsAsync([
	        [ "funcId", [ "values", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
	                      11, 12, 13, 14, 15, 16, 17, 18, 19 ] ],
	        [ "options", [ "or",
	                       [ "null", 0 ],
	                       [ "values", 0, 1, 2, 3, 4, 5, 6, 7  ] ] ],
	        [ "args", "rest" ]
	    ]);

	    defineFunction("subtotal", function(callback, funcId){
	        var self = this;
	        var ignoreHidden = funcId > 100;
	        if (ignoreHidden) {
	            funcId -= 100;
	        }
	        var args = [];
	        for (var i = 2; i < arguments.length; ++i) {
	            args.push(arguments[i]);
	        }
	        self.resolveCells(args, function(){
	            var values = fetchValuesForAggregate(self, args, ignoreHidden ? 1 : 0);
	            self.func(AGGREGATE_FUNCS[funcId - 1], callback, values);
	        });
	    }).argsAsync([
	        [ "funcId", [ "values", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
	                      101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111 ] ],
	        [ "+", [ "ref", [ "or", "ref", "#matrix" ] ] ]
	    ]);

	    // https://support.office.com/en-sg/article/AVEDEV-function-ec78fa01-4755-466c-9a2b-0c4f9eacaf6d
	    defineFunction("avedev", function(numbers){
	        var avg = numbers.reduce(function(sum, num){
	            return sum + num;
	        }, 0) / numbers.length;
	        return numbers.reduce(function(sum, num){
	            return sum + Math.abs(num - avg);
	        }, 0) / numbers.length;
	    }).args([
	        [ "numbers", [ "collect", "number" ] ],
	        [ "?", [ "assert", "$numbers.length >= 2", "NUM" ] ]
	    ]);

	    function _binom_dist(x, n, p, cumulative) {
	        if (!cumulative) {
	            return _combinations(n, x) * Math.pow(p, x) * Math.pow(1-p, n-x);
	        } else {
	            var sum = 0;
	            for (var j = 0; j <= x; ++j) {
	                sum += _combinations(n, j) * Math.pow(p, j) * Math.pow(1-p, n-j);
	            }
	            return sum;
	        }
	    }

	    defineFunction("binom.dist", _binom_dist).args([
	        [ "successes", "integer+" ],
	        [ "trials", [ "and", "integer", [ "assert", "$trials >= $successes" ] ] ],
	        [ "probability", [ "and", "number", [ "[between]", 0, 1 ] ] ],
	        [ "cumulative", "logical" ]
	    ]);

	    defineAlias("binomdist", "binom.dist");

	    defineFunction("binom.inv", function(n, p, alpha){
	        // XXX: could a binary search be faster?
	        for (var x = 0; x <= n; ++x) {
	            if (_binom_dist(x, n, p, true) >= alpha) {
	                return x;
	            }
	        }
	        return new CalcError("N/A"); // XXX: is this right?
	    }).args([
	        [ "trials", "integer+" ],
	        [ "probability", [ "and", "number", [ "[between]", 0, 1 ] ] ],
	        [ "alpha", [ "and", "number", [ "[between]", 0, 1 ] ] ]
	    ]);

	    defineAlias("critbinom", "binom.inv");

	    defineFunction("binom.dist.range", function(n, p, s, s2){
	        var sum = 0;
	        for (var k = s; k <= s2; ++k) {
	            sum += _combinations(n, k) * Math.pow(p, k) * Math.pow(1-p, n-k);
	        }
	        return sum;
	    }).args([
	        [ "trials", "integer+" ],
	        [ "probability", [ "and", "number", [ "[between]", 0, 1 ] ] ],
	        [ "successes_min", [ "and", "integer", [ "[between]", 0, "$trials" ] ] ],
	        [ "successes_max", [ "or",
	                             [ "and", "integer",
	                               [ "[between]", "$successes_min", "$trials" ] ],
	                             [ "null", "$successes_min" ] ] ]
	    ]);

	    defineFunction("negbinom.dist", function(x, k, p, cumulative){
	        if (cumulative) {
	            var sum = 0;
	            while (x >= 0) {
	                sum += _combinations(x+k-1, x) * Math.pow(p, k) * Math.pow(1-p, x);
	                x--;
	            }
	            return sum;
	        }
	        return _combinations(x+k-1, x) * Math.pow(p, k) * Math.pow(1-p, x);
	    }).args([
	        [ "number_f", "integer+" ],
	        [ "number_s", "integer+" ],
	        [ "probability_s", [ "and", "number", [ "[between]", 0, 1 ] ] ],
	        [ "cumulative", "logical" ]
	    ]);

	    defineAlias("negbinomdist", "negbinom.dist");

	    /* -----[ lookup functions ]----- */

	    defineFunction("address", function(row, col, abs, a1, sheet){
	        // by some lucky coincidence, we get the corret `rel` value by just subtracting 1 from the
	        // abs argument
	        var cell = new CellRef(row - 1, col - 1, abs - 1);
	        if (sheet) {
	            cell.setSheet(sheet, true);
	        }
	        return a1 ? cell.print(0, 0) : cell.print();
	    }).args([
	        [ "row", "integer++" ],
	        [ "col", "integer++" ],
	        [ "abs", [ "or", [ "null", 1 ], [ "values", 1, 2, 3, 4 ]]],
	        [ "a1", [ "or", [ "null", true ], "logical" ]],
	        [ "sheet", [ "or", "null", "string" ]]
	    ]);

	    defineFunction("areas", function(ref){
	        var count = 0;
	        (function loop(x){
	            if (x instanceof CellRef || x instanceof RangeRef) {
	                count++;
	            } else if (x instanceof UnionRef) {
	                x.refs.forEach(loop);
	            }
	            // XXX: NameRef if we add support
	        })(ref);
	        return count;
	    }).args([
	        [ "ref", "ref" ]
	    ]);

	    defineFunction("choose", function(index, args){
	        if (index > args.length) {
	            return new CalcError("N/A");
	        } else {
	            return args[index - 1];
	        }
	    }).args([
	        [ "*index", "integer" ],
	        [ "+", [ "value", "anything" ] ]
	    ]);

	    defineFunction("column", function(ref){
	        if (!ref) {
	            return this.formula.col + 1;
	        }
	        if (ref instanceof CellRef) {
	            return ref.col + 1;
	        }
	        return this.asMatrix(ref).mapCol(function(col){
	            return col + ref.topLeft.col + 1;
	        });
	    }).args([
	        [ "ref", [ "or", "area", "null" ]]
	    ]);

	    defineFunction("columns", function(m){
	        return m instanceof Ref ? m.width() : m.width;
	    }).args([
	        [ "ref", [ "or", "area", "#matrix" ] ]
	    ]);

	    defineFunction("formulatext", function(ref){
	        var cell = this.getRefCells(ref)[0]; // XXX: overkill, but oh well.
	        if (!cell.formula) {
	            return new CalcError("N/A");
	        }
	        return cell.formula.print(cell.row, cell.col);
	    }).args([
	        [ "ref", "ref" ]
	    ]);

	    defineFunction("hlookup", function(value, m, row, approx){
	        var resultCol = null;
	        m.eachCol(function(col){
	            var data = m.get(0, col);
	            if (approx) {
	                if (data > value) {
	                    return true;
	                }
	                resultCol = col;
	            } else if (data === value) {
	                resultCol = col;
	                return true;
	            }
	        });
	        if (resultCol == null) {
	            return new CalcError("N/A");
	        }
	        return m.get(row - 1, resultCol);
	    }).args([
	        [ "value", "anyvalue" ],
	        [ "range", "matrix" ],
	        [ "row", "integer++" ],
	        [ "approx", [ "or", "logical", [ "null", true ]]]
	    ]);

	    defineFunction("index", function(callback, ref, row, col, areanum){
	        var self = this;
	        if (ref instanceof UnionRef) {
	            ref = ref.refs[areanum - 1];
	        }
	        if ((!row && !col) || !ref) {
	            return callback(new CalcError("N/A"));
	        }
	        if (ref instanceof CellRef) {
	            ref = ref.toRangeRef();
	        }
	        if (ref instanceof RangeRef) {
	            if (row && col) {
	                if (col > ref.width() || row > ref.height()) {
	                    return callback(new CalcError("REF"));
	                }
	                // fetching a single cell
	                var cell = ref.toCell(row - 1, col - 1);
	                self.resolveCells([ cell ], function(){
	                    callback(self.getRefData(cell));
	                });
	                return;
	            }
	            if (!row) {
	                // fetch a full column
	                var colRange = ref.toColumn(col - 1);
	                self.resolveCells([ colRange ], function(){
	                    callback(self.asMatrix(colRange));
	                });
	                return;
	            }
	            if (!col) {
	                // fetch a full row
	                var rowRange = ref.toRow(row - 1);
	                self.resolveCells([ rowRange ], function(){
	                    callback(self.asMatrix(rowRange));
	                });
	                return;
	            }
	        }
	        else if (ref instanceof Matrix) {
	            if (ref.width > 1 && ref.height > 1) {
	                if (row && col) {
	                    return callback(ref.get(row - 1, col - 1));
	                }
	                if (!row) {
	                    return callback(ref.mapRow(function(row){
	                        return ref.get(row, col - 1);
	                    }));
	                }
	                if (!col) {
	                    return callback(ref.mapCol(function(col){
	                        return ref.get(row - 1, col);
	                    }));
	                }
	            }
	            if (ref.width == 1) {
	                return callback(ref.get(row - 1, 0));
	            }
	            if (ref.height == 1) {
	                return callback(ref.get(0, col - 1));
	            }
	        }
	        else {
	            callback(new CalcError("REF"));
	        }
	    }).argsAsync([
	        [ "range", [ "or", "ref", "matrix" ] ],
	        [ "row", [ "or", "integer+", "null" ] ],
	        [ "col", [ "or", "integer+", "null" ] ],
	        [ "areanum", [ "or", "integer++", [ "null", 1 ] ] ]
	    ]);

	    defineFunction("indirect", function(thing){
	        try {
	            // XXX: does more work than needed.  we could go for parseReference, but that one
	            // doesn't (yet?) support "SheetName!" prefix.
	            var f = this.formula;
	            var exp = calc.parseFormula(f.sheet, f.row, f.col, thing);
	            if (!(exp.ast instanceof Ref)) {
	                throw 1;
	            }
	            return exp.ast.absolute(f.row, f.col);
	        } catch(ex) {
	            return new CalcError("REF");
	        }
	    }).args([
	        [ "thing", "string" ]
	    ]);

	    // XXX: LOOKUP.  seems to be deprecated in favor of HLOOKUP/VLOOKUP

	    // XXX: double-check this one.
	    defineFunction("match", function(val, m, type){
	        var index = 1, cmp;
	        if (type === 0) {
	            cmp = parseCriteria(val);
	        } else if (type === -1) {
	            cmp = parseCriteria("<=" + val);
	        } else if (type === 1) {
	            cmp = parseCriteria(">=" + val);
	        }
	        if (m.each(function(el){
	            if (el != null && cmp(el)) {
	                if (type !== 0 && val != el) {
	                    --index;
	                }
	                return true;
	            }
	            index++;
	        }, true) && index > 0) {
	            return index;
	        } else {
	            return new CalcError("N/A");
	        }
	    }).args([
	        [ "value", "anyvalue" ],
	        [ "range", "matrix" ],
	        [ "type", [ "or",
	                    [ "values", -1, 0, 1 ],
	                    [ "null", 1 ]]]
	    ]);

	    defineFunction("offset", function(ref, rows, cols, height, width){
	        var topLeft = (ref instanceof CellRef ? ref : ref.topLeft).clone();
	        topLeft.row += rows;
	        topLeft.col += cols;
	        if (topLeft.row < 0 || topLeft.col < 0) {
	            return new CalcError("VALUE");
	        }
	        if (height > 1 || width > 1) {
	            return new RangeRef(topLeft, new CellRef(topLeft.row + height - 1,
	                                                     topLeft.col + width - 1))
	                .setSheet(ref.sheet, ref.hasSheet());
	        }
	        return topLeft;
	    }).args([
	        [ "ref", "area" ],
	        [ "*rows", "integer" ],
	        [ "*cols", "integer" ],
	        [ "*height", [ "or", "integer++", [ "null", "$ref.height()" ]]],
	        [ "*width", [ "or", "integer++", [ "null", "$ref.width()" ]]]
	    ]);

	    defineFunction("row", function(ref){
	        if (!ref) {
	            return this.formula.row + 1;
	        }
	        if (ref instanceof CellRef) {
	            return ref.row + 1;
	        }
	        return this.asMatrix(ref).mapRow(function(row){
	            return row + ref.topLeft.row + 1;
	        });
	    }).args([
	        [ "ref", [ "or", "area", "null" ]]
	    ]);

	    defineFunction("rows", function(m){
	        return m instanceof Ref ? m.height() : m.height;
	    }).args([
	        [ "ref", [ "or", "area", "#matrix" ] ]
	    ]);

	    defineFunction("vlookup", function(value, m, col, approx){
	        var resultRow = null;
	        if (typeof value != "number") {
	            approx = false;
	        }
	        if (typeof value == "string") {
	            value = value.toLowerCase();
	        }
	        m.eachRow(function(row){
	            var data = m.get(row, 0);
	            if (approx) {
	                if (data > value) {
	                    return true;
	                }
	                resultRow = row;
	            } else {
	                if (typeof data == "string") {
	                    data = data.toLowerCase();
	                }
	                if (data === value) {
	                    resultRow = row;
	                    return true;
	                }
	            }
	        });
	        if (resultRow == null) {
	            return new CalcError("N/A");
	        }
	        return m.get(resultRow, col - 1);
	    }).args([
	        [ "value", "anyvalue" ],
	        [ "range", "matrix" ],
	        [ "col", "integer++" ],
	        [ "approx", [ "or", "logical", [ "null", true ]]]
	    ]);

	    /* -----[ Date and time functions ]----- */

	    defineFunction("date", function(year, month, date){
	        return packDate(year, month-1, date);
	    }).args([
	        [ "*year", "integer" ],
	        [ "*month", "integer" ],
	        [ "*date", "integer" ]
	    ]);

	    defineFunction("day", function(date){
	        return unpackDate(date).date;
	    }).args([
	        [ "*date", "date" ]
	    ]);

	    defineFunction("month", function(date){
	        return unpackDate(date).month + 1;
	    }).args([
	        [ "*date", "date" ]
	    ]);

	    defineFunction("year", function(date){
	        return unpackDate(date).year;
	    }).args([
	        [ "*date", "date" ]
	    ]);

	    defineFunction("weekday", function(date){
	        // XXX: TODO type
	        return unpackDate(date).day + 1;
	    }).args([
	        [ "*date", "date" ]
	    ]);

	    // https://support.office.com/en-GB/article/WEEKNUM-function-e5c43a03-b4ab-426c-b411-b18c13c75340
	    // XXX: this is a mess.
	    defineFunction("weeknum", function(date, type){
	        var fw = packDate(unpackDate(date).year, 0, 1);
	        var sy = unpackDate(fw);
	        var diff;
	        if (type == 21) {
	            // Monday-based weeks, first week is the one containing the first Thursday of the year
	            // we want to place in fw and sy the first Thursday
	            diff = 3 - (sy.day + 6) % 7;
	            if (diff < 0) {
	                diff += 7;
	            }
	            fw += diff;
	            sy.date += diff;
	            sy.day = 4;         // Thursday.
	            type = 1;
	        } else {
	            if (type == 1) {
	                type = 0;
	            } else if (type == 2) {
	                type = 1;
	            } else {
	                type = (type - 10) % 7;
	            }
	            // finally compatible with what we got:
	            // type == 0 means week starts on Sunday
	            //         1                      Monday
	            //         2                      Tuesday
	            // ...
	        }
	        diff = sy.day - type;
	        if (diff < 0) {
	            diff += 7;
	        }
	        fw -= diff;
	        return Math.ceil((date + 1 - fw) / 7);
	    }).args([
	        [ "*date", "date" ],
	        [ "*type", [ "or", [ "null", 1 ],
	                     [ "values", 1, 2, 11, 12, 13, 14, 15, 16, 17, 21 ] ] ]
	    ]);

	    function weeksInYear(year) {
	        var d = unpackDate(packDate(year, 0, 1));
	        if ((d.day == 4) || (d.day == 3 && runtime.isLeapYear(year))) {
	            // long year
	            return 53;
	        }
	        return 52;
	    }

	    defineFunction("isoweeknum", function isoweeknum(date){
	        // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_of_a_given_date
	        var d = unpackDate(date);
	        var dow = d.day || 7;
	        var wk = Math.floor((d.ord - dow + 10) / 7);
	        if (wk < 1) {
	            return weeksInYear(d.year - 1);
	        } else if (wk == 53 && wk > weeksInYear(d.year)) {
	            return 1;
	        }
	        return wk;
	    }).args([
	        [ "*date", "date" ]
	    ]);

	    defineFunction("now", function(){
	        return runtime.dateToSerial(new Date());
	    }).args([]);

	    defineFunction("today", function(){
	        return runtime.dateToSerial(new Date()) | 0;
	    }).args([]);

	    defineFunction("time", function(hh, mm, ss){
	        return runtime.packTime(hh, mm, ss, 0);
	    }).args([
	        [ "*hours", "integer" ],
	        [ "*minutes", "integer" ],
	        [ "*seconds", "integer" ]
	    ]);

	    defineFunction("hour", function(time){
	        return runtime.unpackTime(time).hours;
	    }).args([
	        [ "*time", "datetime" ]
	    ]);

	    defineFunction("minute", function(time){
	        return runtime.unpackTime(time).minutes;
	    }).args([
	        [ "*time", "datetime" ]
	    ]);

	    defineFunction("second", function(time){
	        return runtime.unpackTime(time).seconds;
	    }).args([
	        [ "*time", "datetime" ]
	    ]);

	    defineFunction("edate", function(base, months){
	        var d = unpackDate(base);
	        var m = d.month + months;
	        var y = d.year + Math.floor(m/12);
	        m %= 12;
	        if (m < 0) {
	            m += 12;
	        }
	        d = Math.min(d.date, daysInMonth(y, m));
	        return packDate(y, m, d);
	    }).args([
	        [ "*start_date", "date" ],
	        [ "*months", "integer" ]
	    ]);

	    defineFunction("eomonth", function(base, months){
	        var d = unpackDate(base);
	        var m = d.month + months;
	        var y = d.year + Math.floor(m/12);
	        m %= 12;
	        if (m < 0) {
	            m += 12;
	        }
	        d = daysInMonth(y, m);
	        return packDate(y, m, d);
	    }).args([
	        [ "*start_date", "date" ],
	        [ "*months", "integer" ]
	    ]);

	    defineFunction("workday", function(date, n, holidays){
	        // XXX: the algorithm here is pretty dumb, can we do better?
	        var inc = n > 0 ? 1 : -1;
	        n = Math.abs(n);
	        var dow = unpackDate(date).day;
	        while (n > 0) {
	            date += inc;
	            dow = (dow + inc) % 7;
	            if (dow > 0 && dow < 6 && holidays.indexOf(date) < 0) {
	                --n;
	            }
	        }
	        return date;
	    }).args([
	        [ "start_date", "date" ],
	        [ "days", "integer" ],
	        [ "holidays", [ "collect", "date" ] ]
	    ]);

	    defineFunction("networkdays", function(date, end, holidays){
	        // XXX: the algorithm here is pretty dumb, can we do better?
	        if (date > end) {
	            var tmp = date;
	            date = end;
	            end = tmp;
	        }
	        var count = 0;
	        var dow = unpackDate(date).day;
	        while (date <= end) {
	            if (dow > 0 && dow < 6 && holidays.indexOf(date) < 0) {
	                count++;
	            }
	            date++;
	            dow = (dow + 1) % 7;
	        }
	        return count;
	    }).args([
	        [ "start_date", "date" ],
	        [ "end_date", "date" ],
	        [ "holidays", [ "collect", "date" ] ]
	    ]);

	    defineFunction("days", function(start, end){
	        return end - start;
	    }).args([
	        [ "*start_date", "date" ],
	        [ "*end_date", "date" ]
	    ]);

	    function _days_360(start, end, method) {
	        var d1 = unpackDate(start);
	        var d2 = unpackDate(end);

	        // https://en.wikipedia.org/wiki/360-day_calendar
	        // humanity is a mess.
	        if (method) {
	            // EU method
	            if (d1.date == 31) {
	                d1.date = 30;
	            }
	            if (d2.date == 31) {
	                d2.date = 30;
	            }
	        } else {
	            // US method
	            if (d1.month == 1 && d2.month == 1
	                && d1.date == daysInMonth(d1.year, 1)
	                && d2.date == daysInMonth(d2.year, 1)) {
	                d2.date = 30;
	            }
	            if (d1.date == daysInMonth(d1.year, d1.month)) {
	                d1.date = 30;
	                if (d2.date == 31) {
	                    d2.date = 30;
	                }
	            } else {
	                if (d1.date == 30 && d2.date == 31) {
	                    d2.date = 30;
	                }
	            }
	        }

	        return (360 * (d2.year - d1.year)
	                + 30 * (d2.month - d1.month)
	                + (d2.date - d1.date));
	    }

	    runtime._days_360 = _days_360;

	    defineFunction("days360", _days_360).args([
	        [ "*start_date", "date" ],
	        [ "*end_date", "date" ],
	        [ "*method", [ "or", "logical", [ "null", false ] ] ]
	    ]);

	    defineFunction("yearfrac", function(start, end, method){
	        switch (method) {
	          case 0:
	            return _days_360(start, end, false) / 360;
	          case 1:
	            return (end - start) / daysInYear(unpackDate(start).year);
	          case 2:
	            return (end - start) / 360;
	          case 3:
	            return (end - start) / 365;
	          case 4:
	            return _days_360(start, end, true) / 360;
	        }
	    }).args([
	        [ "*start_date", "date" ],
	        [ "*end_date", "date" ],
	        [ "*method", [ "or", [ "null", 0 ], [ "values", 0, 1, 2, 3, 4 ] ] ]
	    ]);

	    defineFunction("datevalue", function(text){
	        var date = runtime.parseDate(text);
	        if (date) {
	            return runtime.dateToSerial(date);
	        }
	        return new CalcError("VALUE");
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    defineFunction("timevalue", function(text){
	        var m = text.toLowerCase().match(/(\d+):(\d+)(:(\d+)(\.(\d+))?)?\s*(am?|pm?)?/);
	        if (m) {
	            var hh = parseFloat(m[1]);
	            var mm = parseFloat(m[2]);
	            var ss = m[3] ? parseFloat(m[4]) : 0;
	            //var ms = m[5] ? parseFloat(m[6]) : 0;
	            var ampm = m[7];
	            if (ampm && (hh > 12 || hh < 1)) {
	                return new CalcError("VALUE");
	            }
	            if (/^p/.test(ampm)) {
	                hh += 12;
	            }
	            return runtime.packTime(hh, mm, ss, 0);
	        }
	        return new CalcError("VALUE");
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    /* -----[ Matrix functions ]----- */

	    defineFunction("mdeterm", function(m){
	        var error = m.each(function(val){
	            if (typeof val != "number") {
	                return new CalcError("VALUE");
	            }
	        }, true);
	        return error || m.determinant();
	    }).args([
	        [ "m", [ "and", "matrix",
	                 [ "assert", "$m.width == $m.height" ] ] ]
	    ]);

	    defineFunction("transpose", function(m){
	        return m.transpose();
	    }).args([
	        [ "range", "matrix" ]
	    ]);

	    defineFunction("mmult", function(a, b){
	        return a.multiply(b);
	    }).args([
	        [ "a", "matrix" ],
	        [ "b", [ "and", "matrix",
	                 [ "assert", "$b.height == $a.width" ] ] ]
	    ]);

	    defineFunction("munit", function(n){
	        return new Matrix(this).unit(n);
	    }).args([
	        [ "n", "integer+" ]
	    ]);

	    defineFunction("minverse", function(m){
	        var error = m.each(function(val){
	            if (typeof val != "number") {
	                return new CalcError("VALUE");
	            }
	        }, true);
	        return error || m.inverse() || new CalcError("VALUE");
	    }).args([
	        [ "m", [ "and", "matrix",
	                 [ "assert", "$m.width == $m.height" ] ] ]
	    ]);

	    /* -----[ Other ]----- */

	    defineFunction("rand", function() {
	        return Math.random();
	    }).args([]);

	    defineFunction("randbetween", function(min, max){
	        return min + Math.floor((max - min + 1) * Math.random());
	    }).args([
	        [ "min", "integer" ],
	        [ "max", [ "and", "integer", [ "assert", "$max >= $min" ] ] ]
	    ]);

	    defineFunction("true", function(){
	        return true;
	    }).args([]);

	    defineFunction("false", function(){
	        return true;
	    }).args([]);

	    defineFunction("roman", function(num){
	        return util.arabicToRoman(num).toUpperCase();
	    }).args([
	        [ "*number", "integer" ]
	    ]);

	    defineFunction("arabic", function(rom){
	        var num = util.romanToArabic(rom);
	        return num == null ? new CalcError("VALUE") : num;
	    }).args([
	        [ "*roman", "string" ]
	    ]);

	    defineFunction("base", function(number, radix, minLen){
	        var str = number.toString(radix).toUpperCase();
	        while (str.length < minLen) {
	            str = "0" + str;
	        }
	        return str;
	    }).args([
	        [ "*number", "integer" ],
	        [ "*radix", [ "and", "integer", [ "[between]", 2, 36 ] ] ],
	        [ "*minLen", [ "or", "integer+", [ "null", 0 ] ] ]
	    ]);

	    defineFunction("decimal", function(text, radix){
	        text = text.toUpperCase();
	        var val = 0;
	        for (var i = 0; i < text.length; ++i) {
	            var d = text.charCodeAt(i);
	            if (d >= 48 && d <= 57) {
	                d -= 48;
	            } else if (d >= 65 && d < (55 + radix)) {
	                d -= 55;
	            } else {
	                return new CalcError("VALUE");
	            }
	            val = val * radix + d;
	        }
	        return val;
	    }).args([
	        [ "*text", "string" ],
	        [ "*radix", [ "and", "integer", [ "[between]", 2, 36 ] ] ]
	    ]);

	    /* -----[ String functions ]----- */

	    defineFunction("char", function(code){
	        return String.fromCharCode(code);
	    }).args([
	        [ "*code", "integer+" ]
	    ]);

	    // From XRegExp
	    var RX_NON_PRINTABLE = /[\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g;

	    defineFunction("clean", function(text){
	        return text.replace(RX_NON_PRINTABLE, "");
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    defineFunction("code", function(text){
	        return text.charAt(0);
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    defineAlias("unichar", "char");
	    defineAlias("unicode", "code");

	    defineFunction("concatenate", function(args){
	        var out = "";
	        for (var i = 0; i < args.length; ++i) {
	            out += args[i];
	        }
	        return out;
	    }).args([
	        [ "+",
	          [ "*text", "string" ] ]
	    ]);

	    defineFunction("dollar", function(number, decimals){
	        var format = "$#,##0DECIMALS;($#,##0DECIMALS)";
	        var dec = "";
	        var denomitator = 1;

	        while (decimals-- > 0) {
	          dec += "0";
	        }

	        while (++decimals < 0) {
	          denomitator *= 10;
	        }

	        if (dec !== "") {
	          dec = "." + dec;
	        } else if (denomitator !== 1) {
	            number = Math.round(number / denomitator) * denomitator;
	        }

	        format = format.replace(/DECIMALS/g, dec);

	        return spreadsheet.formatting.text(number, format);
	    }).args([
	        [ "*number", "number" ],
	        [ "*decimals", [ "or", "integer", [ "null", 2 ] ] ]
	    ]);

	    defineFunction("exact", function(a, b){
	        return a === b;
	    }).args([
	        [ "*text1", "string" ],
	        [ "*text2", "string" ]
	    ]);

	    defineFunction("find", function(substring, string, start){
	        var pos = string.indexOf(substring, start - 1);
	        return pos < 0 ? new CalcError("VALUE") : pos + 1;
	    }).args([
	        [ "*substring", "string" ],
	        [ "*string", "string" ],
	        [ "*start", [ "or", "integer++", [ "null", 1 ] ] ]
	    ]);

	    defineFunction("fixed", function(number, decimals, noCommas){
	        var scale = Math.pow(10, decimals);
	        number = Math.round(number * scale) / scale;
	        var format = noCommas ? "0" : "#,##0";
	        if (decimals > 0) {
	            format += ".";
	            while (decimals-- > 0) { format += "0"; }
	        }
	        return spreadsheet.formatting.text(number, format);
	    }).args([
	        [ "*number", "number" ],
	        [ "*decimals", [ "or", "integer", [ "null", 2 ] ] ],
	        [ "*noCommas", [ "or", "boolean", [ "null", false ] ] ]
	    ]);

	    defineFunction("left", function(text, length){
	        return text.substr(0, length);
	    }).args([
	        [ "*text", "string" ],
	        [ "*length", [ "or", "integer+", [ "null", 1 ] ] ]
	    ]);

	    defineFunction("right", function(text, length){
	        return text.substr(-length);
	    }).args([
	        [ "*text", "string" ],
	        [ "*length", [ "or", "integer+", [ "null", 1 ] ] ]
	    ]);

	    defineFunction("len", function(text){
	        return text.length;
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    defineFunction("lower", function(text){
	        return text.toLowerCase();
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    defineFunction("upper", function(text){
	        return text.toUpperCase();
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    defineFunction("ltrim", function(text){
	        return text.replace(/^\s+/, "");
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    defineFunction("rtrim", function(text){
	        return text.replace(/\s+$/, "");
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    defineFunction("trim", function(text){
	        return text.replace(/^\s+|\s+$/, "");
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    defineFunction("mid", function(text, start, length){
	        return text.substr(start - 1, length);
	    }).args([
	        [ "*text", "string" ],
	        [ "*start", "integer++" ],
	        [ "*length", "integer+" ]
	    ]);

	    defineFunction("proper", function(text){
	        return text.toLowerCase().replace(/\b./g, function(s){
	            return s.toUpperCase();
	        });
	    }).args([
	        [ "*text", "string" ]
	    ]);

	    defineFunction("replace", function(text, start, length, newText){
	        return text.substr(0, --start) + newText + text.substr(start + length);
	    }).args([
	        [ "*text", "string" ],
	        [ "*start", "integer++" ],
	        [ "*length", "integer+" ],
	        [ "*newText", "string" ]
	    ]);

	    defineFunction("rept", function(text, number){
	        var out = "";
	        while (number-- > 0) { out += text; }
	        return out;
	    }).args([
	        [ "*text", "string" ],
	        [ "*number", "integer+" ]
	    ]);

	    defineFunction("search", function(substring, string, start){
	        var pos = string.toLowerCase().indexOf(substring.toLowerCase(), start - 1);
	        return pos < 0 ? new CalcError("VALUE") : pos + 1;
	    }).args([
	        [ "*substring", "string" ],
	        [ "*string", "string" ],
	        [ "*start", [ "or", "integer++", [ "null", 1 ] ] ]
	    ]);

	    defineFunction("substitute", function(text, oldText, newText, nth){
	        if (oldText === newText) {
	            return text;
	        }
	        var pos = -1;
	        function replace() {
	            text = text.substring(0, pos) + newText + text.substring(pos + oldText.length);
	        }
	        while ((pos = text.indexOf(oldText, pos + 1)) >= 0) {
	            if (nth == null) {
	                replace();
	            } else if (--nth === 0) {
	                replace();
	                break;
	            }
	        }
	        return text;
	    }).args([
	        [ "*text", "string" ],
	        [ "*oldText", "string" ],
	        [ "*newText", "string" ],
	        [ "*nth", [ "or", "integer++", "null" ] ]
	    ]);

	    defineFunction("t", function(value){
	        return typeof value == "string" ? value : "";
	    }).args([
	        [ "*value", "anyvalue" ]
	    ]);

	    defineFunction("text", function(value, format){
	        return spreadsheet.formatting.text(value, format);
	    }).args([
	        [ "*value", "anyvalue" ],
	        [ "*format", "string" ]
	    ]);

	    defineFunction("value", function(value){
	        if (typeof value == "number") {
	            return value;
	        }
	        if (typeof value == "boolean") {
	            return +value;
	        }
	        // XXX: this is dirty.  we need it so we can parse i.e. "$12,345.50"
	        value = (value+"").replace(/[$€,]/g, "");
	        value = parseFloat(value);
	        return isNaN(value) ? new CalcError("VALUE") : value;
	    }).args([
	        [ "*value", "anyvalue" ]
	    ]);

	    function Hyperlink(link, text) {
	        this.link = link;
	        this.text = text;
	    }
	    Hyperlink.prototype.toString = function() {
	        return this.text;
	    };

	    defineFunction("hyperlink", function(link, text){
	        return new Hyperlink(link, text);
	    }).args([
	        [ "*link", "string" ],
	        [ "*text", [ "or", "string", [ "null", "$link" ] ] ]
	    ]);

	    //// other misc functions

	    defineFunction("iferror", function(value, valueIfError){
	        return value instanceof CalcError ? valueIfError : value;
	    }).args([
	        [ "*value", "forced!" ],
	        [ "*value_if_error", "anyvalue!" ]
	    ]);

	    //// utils

	    var parseCriteria = (function(){
	        var RXCACHE = Object.create(null);

	        function makeComparator(cmp, x) {
	            if (typeof x == "string") {
	                var num = parseFloat(x);
	                if (!isNaN(num) && num == x) {
	                    x = num;
	                }
	            }
	            return function(a) {
	                var b = x;
	                if (typeof a == "string" && typeof b == "string") {
	                    a = a.toLowerCase();
	                    b = b.toLowerCase();
	                }
	                return cmp(a, b);
	            };
	        }

	        function lc(a) {
	            var num, str;
	            if (typeof a == "string") {
	                a = a.toLowerCase();
	            }
	            if (/^[0-9.]+%$/.test(a)) {
	                str = a.substr(0, a.length - 1);
	                num = parseFloat(str);
	                if (!isNaN(num) && num == str) {
	                    a = num / 100;
	                }
	            } else if (/^[0-9.]+$/.test(a)) {
	                num = parseFloat(a);
	                if (!isNaN(num) && num == a) {
	                    a = num;
	                }
	            }
	            return a;
	        }

	        function compLT(a, b) { return lc(a) < lc(b); }
	        function compLTE(a, b) { return lc(a) <= lc(b); }
	        function compGT(a, b) { return lc(a) > lc(b); }
	        function compGTE(a, b) { return lc(a) >= lc(b); }
	        function compNE(a, b) { return !compEQ(a, b); }
	        function compEQ(a, b) {
	            if (b instanceof RegExp) {
	                return b.test(a);
	            }
	            if (typeof a == "string" || typeof b == "string") {
	                // if either one is string, make sure both are strings
	                a = String(a);
	                b = String(b);
	            }
	            return lc(a) == lc(b);
	        }

	        return function(cmp) {
	            if (typeof cmp == "function") {
	                return cmp;
	            }
	            var m;
	            if ((m = /^=(.*)$/.exec(cmp))) {
	                return makeComparator(compEQ, m[1]);
	            }
	            if ((m = /^<>(.*)$/.exec(cmp))) {
	                return makeComparator(compNE, m[1]);
	            }
	            if ((m = /^<=(.*)$/.exec(cmp))) {
	                return makeComparator(compLTE, m[1]);
	            }
	            if ((m = /^<(.*)$/.exec(cmp))) {
	                return makeComparator(compLT, m[1]);
	            }
	            if ((m = /^>=(.*)$/.exec(cmp))) {
	                return makeComparator(compGTE, m[1]);
	            }
	            if ((m = /^>(.*)$/.exec(cmp))) {
	                return makeComparator(compGT, m[1]);
	            }
	            if (/[?*]/.exec(cmp)) {
	                // has wildchars
	                var rx = RXCACHE[cmp];
	                if (!rx) {
	                    rx = cmp.replace(/(~\?|~\*|[\]({\+\.\|\^\$\\})\[]|[?*])/g, function(s){
	                        switch (s) {
	                          case "~?" : return "\\?";
	                          case "~*" : return "\\*";
	                          case "?"  : return ".";
	                          case "*"  : return ".*";
	                          default   : return "\\" + s;
	                        }
	                    });
	                    rx = RXCACHE[cmp] = new RegExp("^" + rx + "$", "i");
	                }
	                return makeComparator(compEQ, rx);
	            }
	            return makeComparator(compEQ, cmp);
	        };
	    })();

	    function numericPredicate(val) {
	        return typeof val == "number"
	            || typeof val == "boolean"
	            || val == null
	            || val === "";
	    }

	    function ascending(a, b) {
	        return a === b ? 0 : a < b ? -1 : 1;
	    }

	    function descending(a, b) {
	        return a === b ? 0 : a < b ? 1 : -1;
	    }

	    // var m = new Matrix();
	    // m.data = [[1, 2, 3], [4, 1, 6], [7, 8, 9]];
	    // //m.data = [[0, 2, 1], [2, 6, 1], [1, 1, 4]];
	    // //m.data = [[1,4,1,1], [1,4,0,1], [2,3,1,2], [3,2,6,4]];
	    // m.width = m.height = 3;
	    // var x = m.inverse();
	    // x.dump();

	}, __webpack_require__(3));


/***/ })

/******/ });