[Pkg-javascript-commits] [less.js] 60/285: move functions into its own folder and give it a set interface

Jonas Smedegaard dr at jones.dk
Mon Oct 26 23:23:38 UTC 2015


This is an automated email from the git hooks/post-receive script.

js pushed a commit to annotated tag v2.0.0
in repository less.js.

commit d7e7ddba44bbb71daf7909d52b7921bde3e5bf67
Author: Luke Page <luke.a.page at gmail.com>
Date:   Sat Aug 16 13:17:35 2014 +0100

    move functions into its own folder and give it a set interface
---
 lib/less/browser.js                     |   8 +-
 lib/less/functions.js                   | 736 --------------------------------
 lib/less/functions/color-blending.js    |  74 ++++
 lib/less/functions/color.js             | 272 ++++++++++++
 lib/less/functions/data-uri.js          |  69 +++
 lib/less/functions/default.js           |  25 ++
 lib/less/functions/function-caller.js   |  15 +
 lib/less/functions/function-registry.js |  18 +
 lib/less/functions/index.js             |  18 +
 lib/less/functions/math.js              |  42 ++
 lib/less/functions/number.js            |  74 ++++
 lib/less/functions/string.js            |  30 ++
 lib/less/functions/svg.js               |  77 ++++
 lib/less/functions/types.js             |  64 +++
 lib/less/non-node-index.js              |   4 +-
 lib/less/tree.js                        |  12 +-
 lib/less/tree/call.js                   |  12 +-
 test/less-test.js                       |  20 +-
 18 files changed, 807 insertions(+), 763 deletions(-)

diff --git a/lib/less/browser.js b/lib/less/browser.js
index 2023c54..aed3d99 100644
--- a/lib/less/browser.js
+++ b/lib/less/browser.js
@@ -54,11 +54,7 @@ less.poll = less.poll || (isFileProtocol ? 1000 : 1500);
 
 //Setup user functions
 if (options.functions) {
-    for(var func in options.functions) {
-        if (options.functions.hasOwnProperty(func)) {
-            less.tree.functions[func] = options.functions[func];
-        }
-   }
+    less.functions.functionRegistry.addMultiple(options.functions);
 }
 
 var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);
@@ -352,7 +348,7 @@ function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) {
     }
 
     less.environment.loadFile(env, sheet.href, null, function loadInitialFileCallback(e, data, path, webInfo) {
-    
+
         var newFileInfo = {
             currentDirectory: less.environment.getPath(env, path),
             filename: path,
diff --git a/lib/less/functions.js b/lib/less/functions.js
deleted file mode 100644
index e55f6af..0000000
--- a/lib/less/functions.js
+++ /dev/null
@@ -1,736 +0,0 @@
-module.exports = function (less, tree) {
-
-var functions = {
-    rgb: function (r, g, b) {
-        return this.rgba(r, g, b, 1.0);
-    },
-    rgba: function (r, g, b, a) {
-        var rgb = [r, g, b].map(function (c) { return scaled(c, 255); });
-        a = number(a);
-        return new(tree.Color)(rgb, a);
-    },
-    hsl: function (h, s, l) {
-        return this.hsla(h, s, l, 1.0);
-    },
-    hsla: function (h, s, l, a) {
-        function hue(h) {
-            h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h);
-            if      (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; }
-            else if (h * 2 < 1) { return m2; }
-            else if (h * 3 < 2) { return m1 + (m2 - m1) * (2/3 - h) * 6; }
-            else                { return m1; }
-        }
-
-        h = (number(h) % 360) / 360;
-        s = clamp(number(s)); l = clamp(number(l)); a = clamp(number(a));
-
-        var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
-        var m1 = l * 2 - m2;
-
-        return this.rgba(hue(h + 1/3) * 255,
-                         hue(h)       * 255,
-                         hue(h - 1/3) * 255,
-                         a);
-    },
-
-    hsv: function(h, s, v) {
-        return this.hsva(h, s, v, 1.0);
-    },
-
-    hsva: function(h, s, v, a) {
-        h = ((number(h) % 360) / 360) * 360;
-        s = number(s); v = number(v); a = number(a);
-
-        var i, f;
-        i = Math.floor((h / 60) % 6);
-        f = (h / 60) - i;
-
-        var vs = [v,
-                  v * (1 - s),
-                  v * (1 - f * s),
-                  v * (1 - (1 - f) * s)];
-        var perm = [[0, 3, 1],
-                    [2, 0, 1],
-                    [1, 0, 3],
-                    [1, 2, 0],
-                    [3, 1, 0],
-                    [0, 1, 2]];
-
-        return this.rgba(vs[perm[i][0]] * 255,
-                         vs[perm[i][1]] * 255,
-                         vs[perm[i][2]] * 255,
-                         a);
-    },
-
-    hue: function (color) {
-        return new(tree.Dimension)(color.toHSL().h);
-    },
-    saturation: function (color) {
-        return new(tree.Dimension)(color.toHSL().s * 100, '%');
-    },
-    lightness: function (color) {
-        return new(tree.Dimension)(color.toHSL().l * 100, '%');
-    },
-    hsvhue: function(color) {
-        return new(tree.Dimension)(color.toHSV().h);
-    },
-    hsvsaturation: function (color) {
-        return new(tree.Dimension)(color.toHSV().s * 100, '%');
-    },
-    hsvvalue: function (color) {
-        return new(tree.Dimension)(color.toHSV().v * 100, '%');
-    },
-    red: function (color) {
-        return new(tree.Dimension)(color.rgb[0]);
-    },
-    green: function (color) {
-        return new(tree.Dimension)(color.rgb[1]);
-    },
-    blue: function (color) {
-        return new(tree.Dimension)(color.rgb[2]);
-    },
-    alpha: function (color) {
-        return new(tree.Dimension)(color.toHSL().a);
-    },
-    luma: function (color) {
-        return new(tree.Dimension)(color.luma() * color.alpha * 100, '%');
-    },
-    luminance: function (color) {
-        var luminance =
-            (0.2126 * color.rgb[0] / 255)
-          + (0.7152 * color.rgb[1] / 255)
-          + (0.0722 * color.rgb[2] / 255);
-
-        return new(tree.Dimension)(luminance * color.alpha * 100, '%');
-    },
-    saturate: function (color, amount) {
-        // filter: saturate(3.2);
-        // should be kept as is, so check for color
-        if (!color.rgb) {
-            return null;
-        }
-        var hsl = color.toHSL();
-
-        hsl.s += amount.value / 100;
-        hsl.s = clamp(hsl.s);
-        return hsla(hsl);
-    },
-    desaturate: function (color, amount) {
-        var hsl = color.toHSL();
-
-        hsl.s -= amount.value / 100;
-        hsl.s = clamp(hsl.s);
-        return hsla(hsl);
-    },
-    lighten: function (color, amount) {
-        var hsl = color.toHSL();
-
-        hsl.l += amount.value / 100;
-        hsl.l = clamp(hsl.l);
-        return hsla(hsl);
-    },
-    darken: function (color, amount) {
-        var hsl = color.toHSL();
-
-        hsl.l -= amount.value / 100;
-        hsl.l = clamp(hsl.l);
-        return hsla(hsl);
-    },
-    fadein: function (color, amount) {
-        var hsl = color.toHSL();
-
-        hsl.a += amount.value / 100;
-        hsl.a = clamp(hsl.a);
-        return hsla(hsl);
-    },
-    fadeout: function (color, amount) {
-        var hsl = color.toHSL();
-
-        hsl.a -= amount.value / 100;
-        hsl.a = clamp(hsl.a);
-        return hsla(hsl);
-    },
-    fade: function (color, amount) {
-        var hsl = color.toHSL();
-
-        hsl.a = amount.value / 100;
-        hsl.a = clamp(hsl.a);
-        return hsla(hsl);
-    },
-    spin: function (color, amount) {
-        var hsl = color.toHSL();
-        var hue = (hsl.h + amount.value) % 360;
-
-        hsl.h = hue < 0 ? 360 + hue : hue;
-
-        return hsla(hsl);
-    },
-    //
-    // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
-    // http://sass-lang.com
-    //
-    mix: function (color1, color2, weight) {
-        if (!weight) {
-            weight = new(tree.Dimension)(50);
-        }
-        var p = weight.value / 100.0;
-        var w = p * 2 - 1;
-        var a = color1.toHSL().a - color2.toHSL().a;
-
-        var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
-        var w2 = 1 - w1;
-
-        var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2,
-                   color1.rgb[1] * w1 + color2.rgb[1] * w2,
-                   color1.rgb[2] * w1 + color2.rgb[2] * w2];
-
-        var alpha = color1.alpha * p + color2.alpha * (1 - p);
-
-        return new(tree.Color)(rgb, alpha);
-    },
-    greyscale: function (color) {
-        return this.desaturate(color, new(tree.Dimension)(100));
-    },
-    contrast: function (color, dark, light, threshold) {
-        // filter: contrast(3.2);
-        // should be kept as is, so check for color
-        if (!color.rgb) {
-            return null;
-        }
-        if (typeof light === 'undefined') {
-            light = this.rgba(255, 255, 255, 1.0);
-        }
-        if (typeof dark === 'undefined') {
-            dark = this.rgba(0, 0, 0, 1.0);
-        }
-        //Figure out which is actually light and dark!
-        if (dark.luma() > light.luma()) {
-            var t = light;
-            light = dark;
-            dark = t;
-        }
-        if (typeof threshold === 'undefined') {
-            threshold = 0.43;
-        } else {
-            threshold = number(threshold);
-        }
-        if (color.luma() < threshold) {
-            return light;
-        } else {
-            return dark;
-        }
-    },
-    e: function (str) {
-        return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str.value);
-    },
-    escape: function (str) {
-        return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
-    },
-    replace: function (string, pattern, replacement, flags) {
-        var result = string.value;
-
-        result = result.replace(new RegExp(pattern.value, flags ? flags.value : ''), replacement.value);
-        return new(tree.Quoted)(string.quote || '', result, string.escaped);
-    },
-    '%': function (string /* arg, arg, ...*/) {
-        var args = Array.prototype.slice.call(arguments, 1),
-            result = string.value;
-
-        for (var i = 0; i < args.length; i++) {
-            /*jshint loopfunc:true */
-            result = result.replace(/%[sda]/i, function(token) {
-                var value = token.match(/s/i) ? args[i].value : args[i].toCSS();
-                return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value;
-            });
-        }
-        result = result.replace(/%%/g, '%');
-        return new(tree.Quoted)(string.quote || '', result, string.escaped);
-    },
-    unit: function (val, unit) {
-        if(!(val instanceof tree.Dimension)) {
-            throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof tree.Operation ? ". Have you forgotten parenthesis?" : "") };
-        }
-        if (unit) {
-            if (unit instanceof tree.Keyword) {
-                unit = unit.value;
-            } else {
-                unit = unit.toCSS();
-            }
-        } else {
-            unit = "";
-        }
-        return new(tree.Dimension)(val.value, unit);
-    },
-    convert: function (val, unit) {
-        return val.convertTo(unit.value);
-    },
-    round: function (n, f) {
-        var fraction = typeof(f) === "undefined" ? 0 : f.value;
-        return _math(function(num) { return num.toFixed(fraction); }, null, n);
-    },
-    pi: function () {
-        return new(tree.Dimension)(Math.PI);
-    },
-    mod: function(a, b) {
-        return new(tree.Dimension)(a.value % b.value, a.unit);
-    },
-    pow: function(x, y) {
-        if (typeof x === "number" && typeof y === "number") {
-            x = new(tree.Dimension)(x);
-            y = new(tree.Dimension)(y);
-        } else if (!(x instanceof tree.Dimension) || !(y instanceof tree.Dimension)) {
-            throw { type: "Argument", message: "arguments must be numbers" };
-        }
-
-        return new(tree.Dimension)(Math.pow(x.value, y.value), x.unit);
-    },
-    _minmax: function (isMin, args) {
-        args = Array.prototype.slice.call(args);
-        switch(args.length) {
-            case 0: throw { type: "Argument", message: "one or more arguments required" };
-        }
-        var i, j, current, currentUnified, referenceUnified, unit, unitStatic, unitClone,
-            order  = [], // elems only contains original argument values.
-            values = {}; // key is the unit.toString() for unified tree.Dimension values,
-                         // value is the index into the order array.
-        for (i = 0; i < args.length; i++) {
-            current = args[i];
-            if (!(current instanceof tree.Dimension)) {
-                if(Array.isArray(args[i].value)) {
-                    Array.prototype.push.apply(args, Array.prototype.slice.call(args[i].value));
-                }
-                continue;
-            }
-            currentUnified = current.unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(current.value, unitClone).unify() : current.unify();
-            unit = currentUnified.unit.toString() === "" && unitStatic !== undefined ? unitStatic : currentUnified.unit.toString();
-            unitStatic = unit !== "" && unitStatic === undefined || unit !== "" && order[0].unify().unit.toString() === "" ? unit : unitStatic;
-            unitClone = unit !== "" && unitClone === undefined ? current.unit.toString() : unitClone;
-            j = values[""] !== undefined && unit !== "" && unit === unitStatic ? values[""] : values[unit];
-            if (j === undefined) {
-                if(unitStatic !== undefined && unit !== unitStatic) {
-                    throw{ type: "Argument", message: "incompatible types" };
-                }
-                values[unit] = order.length;
-                order.push(current);
-                continue;
-            }
-            referenceUnified = order[j].unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(order[j].value, unitClone).unify() : order[j].unify();
-            if ( isMin && currentUnified.value < referenceUnified.value ||
-                !isMin && currentUnified.value > referenceUnified.value) {
-                order[j] = current;
-            }
-        }
-        if (order.length == 1) {
-            return order[0];
-        }
-        args = order.map(function (a) { return a.toCSS(this.env); }).join(this.env.compress ? "," : ", ");
-        return new(tree.Anonymous)((isMin ? "min" : "max") + "(" + args + ")");
-    },
-    min: function () {
-        return this._minmax(true, arguments);
-    },
-    max: function () {
-        return this._minmax(false, arguments);
-    },
-    "get-unit": function (n) {
-        return new(tree.Anonymous)(n.unit);
-    },
-    argb: function (color) {
-        return new(tree.Anonymous)(color.toARGB());
-    },
-    percentage: function (n) {
-        return new(tree.Dimension)(n.value * 100, '%');
-    },
-    color: function(c) {
-        if ((c instanceof tree.Quoted) &&
-            (/^#([a-f0-9]{6}|[a-f0-9]{3})$/i.test(c.value))) {
-            return new(tree.Color)(c.value.slice(1));
-        }
-        if ((c instanceof tree.Color) || (c = tree.Color.fromKeyword(c.value))) {
-            c.keyword = undefined;
-            return c;
-        }
-        throw {
-            type:    "Argument",
-            message: "argument must be a color keyword or 3/6 digit hex e.g. #FFF"
-        };
-    },
-    iscolor: function (n) {
-        return this._isa(n, tree.Color);
-    },
-    isnumber: function (n) {
-        return this._isa(n, tree.Dimension);
-    },
-    isstring: function (n) {
-        return this._isa(n, tree.Quoted);
-    },
-    iskeyword: function (n) {
-        return this._isa(n, tree.Keyword);
-    },
-    isurl: function (n) {
-        return this._isa(n, tree.URL);
-    },
-    ispixel: function (n) {
-        return this.isunit(n, 'px');
-    },
-    ispercentage: function (n) {
-        return this.isunit(n, '%');
-    },
-    isem: function (n) {
-        return this.isunit(n, 'em');
-    },
-    isunit: function (n, unit) {
-        return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? tree.True : tree.False;
-    },
-    _isa: function (n, Type) {
-        return (n instanceof Type) ? tree.True : tree.False;
-    },
-    tint: function(color, amount) {
-        return this.mix(this.rgb(255,255,255), color, amount);
-    },
-    shade: function(color, amount) {
-        return this.mix(this.rgb(0, 0, 0), color, amount);
-    },
-    extract: function(values, index) {
-        index = index.value - 1; // (1-based index)
-        // handle non-array values as an array of length 1
-        // return 'undefined' if index is invalid
-        return Array.isArray(values.value)
-            ? values.value[index] : Array(values)[index];
-    },
-    length: function(values) {
-        var n = Array.isArray(values.value) ? values.value.length : 1;
-        return new tree.Dimension(n);
-    },
-
-    "data-uri": function(mimetypeNode, filePathNode) {
-
-        if (!less.environment.supportsDataURI(this.env)) {
-            return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
-        }
-
-        var mimetype = mimetypeNode.value;
-        var filePath = (filePathNode && filePathNode.value);
-
-        var useBase64 = false;
-
-        if (arguments.length < 2) {
-            filePath = mimetype;
-        }
-
-        var fragmentStart = filePath.indexOf('#');
-        var fragment = '';
-        if (fragmentStart!==-1) {
-            fragment = filePath.slice(fragmentStart);
-            filePath = filePath.slice(0, fragmentStart);
-        }
-
-        if (this.env.isPathRelative(filePath)) {
-            if (this.currentFileInfo.relativeUrls) {
-                filePath = less.environment.join(this.currentFileInfo.currentDirectory, filePath);
-            } else {
-                filePath = less.environment.join(this.currentFileInfo.entryPath, filePath);
-            }
-        }
-
-        // detect the mimetype if not given
-        if (arguments.length < 2) {
-
-            mimetype = less.environment.mimeLookup(this.env, filePath);
-
-            // use base 64 unless it's an ASCII or UTF-8 format
-            var charset = less.environment.charsetLookup(this.env, mimetype);
-            useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
-            if (useBase64) { mimetype += ';base64'; }
-        }
-        else {
-            useBase64 = /;base64$/.test(mimetype);
-        }
-
-        var buf = less.environment.readFileSync(filePath);
-
-        // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
-        // and the --ieCompat flag is enabled, return a normal url() instead.
-        var DATA_URI_MAX_KB = 32,
-            fileSizeInKB = parseInt((buf.length / 1024), 10);
-        if (fileSizeInKB >= DATA_URI_MAX_KB) {
-
-            if (this.env.ieCompat !== false) {
-                if (!this.env.silent) {
-                    console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
-                }
-
-                return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
-            }
-        }
-
-        buf = useBase64 ? buf.toString('base64')
-                        : encodeURIComponent(buf);
-
-        var uri = "\"data:" + mimetype + ',' + buf + fragment + "\"";
-        return new(tree.URL)(new(tree.Anonymous)(uri));
-    },
-
-    "svg-gradient": function(direction) {
-
-        function throwArgumentDescriptor() {
-            throw { type: "Argument", message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" };
-        }
-
-        if (arguments.length < 3) {
-            throwArgumentDescriptor();
-        }
-        var stops = Array.prototype.slice.call(arguments, 1),
-            gradientDirectionSvg,
-            gradientType = "linear",
-            rectangleDimension = 'x="0" y="0" width="1" height="1"',
-            useBase64 = true,
-            renderEnv = {compress: false},
-            returner,
-            directionValue = direction.toCSS(renderEnv),
-            i, color, position, positionValue, alpha;
-
-        switch (directionValue) {
-            case "to bottom":
-                gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
-                break;
-            case "to right":
-                gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
-                break;
-            case "to bottom right":
-                gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
-                break;
-            case "to top right":
-                gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
-                break;
-            case "ellipse":
-            case "ellipse at center":
-                gradientType = "radial";
-                gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
-                rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
-                break;
-            default:
-                throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" };
-        }
-        returner = '<?xml version="1.0" ?>' +
-            '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' +
-            '<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>';
-
-        for (i = 0; i < stops.length; i+= 1) {
-            if (stops[i].value) {
-                color = stops[i].value[0];
-                position = stops[i].value[1];
-            } else {
-                color = stops[i];
-                position = undefined;
-            }
-
-            if (!(color instanceof tree.Color) || (!((i === 0 || i+1 === stops.length) && position === undefined) && !(position instanceof tree.Dimension))) {
-                throwArgumentDescriptor();
-            }
-            positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%";
-            alpha = color.alpha;
-            returner += '<stop offset="' + positionValue + '" stop-color="' + color.toRGB() + '"' + (alpha < 1 ? ' stop-opacity="' + alpha + '"' : '') + '/>';
-        }
-        returner += '</' + gradientType + 'Gradient>' +
-                    '<rect ' + rectangleDimension + ' fill="url(#gradient)" /></svg>';
-
-        if (useBase64) {
-            try {
-                returner = less.environment.encodeBase64(this.env, returner);
-            } catch(e) {
-                useBase64 = false;
-            }
-        }
-
-        returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'";
-        return new(tree.URL)(new(tree.Anonymous)(returner));
-    }
-};
-
-// Math
-
-var mathFunctions = {
- // name,  unit
-    ceil:  null,
-    floor: null,
-    sqrt:  null,
-    abs:   null,
-    tan:   "",
-    sin:   "",
-    cos:   "",
-    atan:  "rad",
-    asin:  "rad",
-    acos:  "rad"
-};
-
-function _math(fn, unit, n) {
-    if (!(n instanceof tree.Dimension)) {
-        throw { type: "Argument", message: "argument must be a number" };
-    }
-    if (unit == null) {
-        unit = n.unit;
-    } else {
-        n = n.unify();
-    }
-    return new(tree.Dimension)(fn(parseFloat(n.value)), unit);
-}
-
-// ~ End of Math
-
-// Color Blending
-// ref: http://www.w3.org/TR/compositing-1
-
-function colorBlend(mode, color1, color2) {
-    var ab = color1.alpha, cb, // backdrop
-        as = color2.alpha, cs, // source
-        ar, cr, r = [];        // result
-
-    ar = as + ab * (1 - as);
-    for (var i = 0; i < 3; i++) {
-        cb = color1.rgb[i] / 255;
-        cs = color2.rgb[i] / 255;
-        cr = mode(cb, cs);
-        if (ar) {
-            cr = (as * cs + ab * (cb
-                - as * (cb + cs - cr))) / ar;
-        }
-        r[i] = cr * 255;
-    }
-
-    return new(tree.Color)(r, ar);
-}
-
-var colorBlendMode = {
-    multiply: function(cb, cs) {
-        return cb * cs;
-    },
-    screen: function(cb, cs) {
-        return cb + cs - cb * cs;
-    },
-    overlay: function(cb, cs) {
-        cb *= 2;
-        return (cb <= 1)
-            ? colorBlendMode.multiply(cb, cs)
-            : colorBlendMode.screen(cb - 1, cs);
-    },
-    softlight: function(cb, cs) {
-        var d = 1, e = cb;
-        if (cs > 0.5) {
-            e = 1;
-            d = (cb > 0.25) ? Math.sqrt(cb)
-                : ((16 * cb - 12) * cb + 4) * cb;
-        }
-        return cb - (1 - 2 * cs) * e * (d - cb);
-    },
-    hardlight: function(cb, cs) {
-        return colorBlendMode.overlay(cs, cb);
-    },
-    difference: function(cb, cs) {
-        return Math.abs(cb - cs);
-    },
-    exclusion: function(cb, cs) {
-        return cb + cs - 2 * cb * cs;
-    },
-
-    // non-w3c functions:
-    average: function(cb, cs) {
-        return (cb + cs) / 2;
-    },
-    negation: function(cb, cs) {
-        return 1 - Math.abs(cb + cs - 1);
-    }
-};
-
-// ~ End of Color Blending
-
-tree.defaultFunc = {
-    eval: function () {
-        var v = this.value_, e = this.error_;
-        if (e) {
-            throw e;
-        }
-        if (v != null) {
-            return v ? tree.True : tree.False;
-        }
-    },
-    value: function (v) {
-        this.value_ = v;
-    },
-    error: function (e) {
-        this.error_ = e;
-    },
-    reset: function () {
-        this.value_ = this.error_ = null;
-    }
-};
-
-function initFunctions() {
-    var f;
-
-    // math
-    for (f in mathFunctions) {
-        if (mathFunctions.hasOwnProperty(f)) {
-            functions[f] = _math.bind(null, Math[f], mathFunctions[f]);
-        }
-    }
-
-    // color blending
-    for (f in colorBlendMode) {
-        if (colorBlendMode.hasOwnProperty(f)) {
-            functions[f] = colorBlend.bind(null, colorBlendMode[f]);
-        }
-    }
-
-    // default
-    f = tree.defaultFunc;
-    functions["default"] = f.eval.bind(f);
-
-} initFunctions();
-
-function hsla(color) {
-    return functions.hsla(color.h, color.s, color.l, color.a);
-}
-
-function scaled(n, size) {
-    if (n instanceof tree.Dimension && n.unit.is('%')) {
-        return parseFloat(n.value * size / 100);
-    } else {
-        return number(n);
-    }
-}
-
-function number(n) {
-    if (n instanceof tree.Dimension) {
-        return parseFloat(n.unit.is('%') ? n.value / 100 : n.value);
-    } else if (typeof(n) === 'number') {
-        return n;
-    } else {
-        throw {
-            error: "RuntimeError",
-            message: "color functions take numbers as parameters"
-        };
-    }
-}
-
-function clamp(val) {
-    return Math.min(1, Math.max(0, val));
-}
-
-tree.fround = function(env, value) {
-    var p = env && env.numPrecision;
-    //add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999....) are properly rounded...
-    return (p == null) ? value : Number((value + 2e-16).toFixed(p));
-};
-
-tree.functionCall = function(env, currentFileInfo, environment) {
-    this.env = env;
-    this.environment = environment;
-    this.currentFileInfo = currentFileInfo;
-};
-
-tree.functionCall.prototype = functions;
-
-return functions;
-
-};
diff --git a/lib/less/functions/color-blending.js b/lib/less/functions/color-blending.js
new file mode 100644
index 0000000..c24b549
--- /dev/null
+++ b/lib/less/functions/color-blending.js
@@ -0,0 +1,74 @@
+module.exports = function(functions, tree) {
+
+    // Color Blending
+    // ref: http://www.w3.org/TR/compositing-1
+
+    function colorBlend(mode, color1, color2) {
+        var ab = color1.alpha, cb, // backdrop
+            as = color2.alpha, cs, // source
+            ar, cr, r = [];        // result
+
+        ar = as + ab * (1 - as);
+        for (var i = 0; i < 3; i++) {
+            cb = color1.rgb[i] / 255;
+            cs = color2.rgb[i] / 255;
+            cr = mode(cb, cs);
+            if (ar) {
+                cr = (as * cs + ab * (cb
+                    - as * (cb + cs - cr))) / ar;
+            }
+            r[i] = cr * 255;
+        }
+
+        return new(tree.Color)(r, ar);
+    }
+
+    var colorBlendModeFunctions = {
+        multiply: function(cb, cs) {
+            return cb * cs;
+        },
+        screen: function(cb, cs) {
+            return cb + cs - cb * cs;
+        },
+        overlay: function(cb, cs) {
+            cb *= 2;
+            return (cb <= 1)
+                ? colorBlendModeFunctions.multiply(cb, cs)
+                : colorBlendModeFunctions.screen(cb - 1, cs);
+        },
+        softlight: function(cb, cs) {
+            var d = 1, e = cb;
+            if (cs > 0.5) {
+                e = 1;
+                d = (cb > 0.25) ? Math.sqrt(cb)
+                    : ((16 * cb - 12) * cb + 4) * cb;
+            }
+            return cb - (1 - 2 * cs) * e * (d - cb);
+        },
+        hardlight: function(cb, cs) {
+            return colorBlendModeFunctions.overlay(cs, cb);
+        },
+        difference: function(cb, cs) {
+            return Math.abs(cb - cs);
+        },
+        exclusion: function(cb, cs) {
+            return cb + cs - 2 * cb * cs;
+        },
+
+        // non-w3c functions:
+        average: function(cb, cs) {
+            return (cb + cs) / 2;
+        },
+        negation: function(cb, cs) {
+            return 1 - Math.abs(cb + cs - 1);
+        }
+    };
+
+    for (var f in colorBlendModeFunctions) {
+        if (colorBlendModeFunctions.hasOwnProperty(f)) {
+            colorBlend[f] = colorBlend.bind(null, colorBlendModeFunctions[f]);
+        }
+    }
+
+    functions.functionRegistry.addMultiple(colorBlend);
+};
diff --git a/lib/less/functions/color.js b/lib/less/functions/color.js
new file mode 100644
index 0000000..1b493bc
--- /dev/null
+++ b/lib/less/functions/color.js
@@ -0,0 +1,272 @@
+module.exports = function(functions, tree) {
+    function clamp(val) {
+        return Math.min(1, Math.max(0, val));
+    }
+    function hsla(color) {
+        return colorFunctions.hsla(color.h, color.s, color.l, color.a);
+    }
+    function number(n) {
+        if (n instanceof tree.Dimension) {
+            return parseFloat(n.unit.is('%') ? n.value / 100 : n.value);
+        } else if (typeof(n) === 'number') {
+            return n;
+        } else {
+            throw {
+                error: "RuntimeError",
+                message: "color functions take numbers as parameters"
+            };
+        }
+    }
+    function scaled(n, size) {
+        if (n instanceof tree.Dimension && n.unit.is('%')) {
+            return parseFloat(n.value * size / 100);
+        } else {
+            return number(n);
+        }
+    }
+    var colorFunctions = {
+        rgb: function (r, g, b) {
+            return colorFunctions.rgba(r, g, b, 1.0);
+        },
+        rgba: function (r, g, b, a) {
+            var rgb = [r, g, b].map(function (c) { return scaled(c, 255); });
+            a = number(a);
+            return new(tree.Color)(rgb, a);
+        },
+        hsl: function (h, s, l) {
+            return colorFunctions.hsla(h, s, l, 1.0);
+        },
+        hsla: function (h, s, l, a) {
+            function hue(h) {
+                h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h);
+                if      (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; }
+                else if (h * 2 < 1) { return m2; }
+                else if (h * 3 < 2) { return m1 + (m2 - m1) * (2/3 - h) * 6; }
+                else                { return m1; }
+            }
+
+            h = (number(h) % 360) / 360;
+            s = clamp(number(s)); l = clamp(number(l)); a = clamp(number(a));
+
+            var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
+            var m1 = l * 2 - m2;
+
+            return colorFunctions.rgba(hue(h + 1/3) * 255,
+                hue(h)       * 255,
+                hue(h - 1/3) * 255,
+                a);
+        },
+
+        hsv: function(h, s, v) {
+            return colorFunctions.hsva(h, s, v, 1.0);
+        },
+
+        hsva: function(h, s, v, a) {
+            h = ((number(h) % 360) / 360) * 360;
+            s = number(s); v = number(v); a = number(a);
+
+            var i, f;
+            i = Math.floor((h / 60) % 6);
+            f = (h / 60) - i;
+
+            var vs = [v,
+                v * (1 - s),
+                v * (1 - f * s),
+                v * (1 - (1 - f) * s)];
+            var perm = [[0, 3, 1],
+                [2, 0, 1],
+                [1, 0, 3],
+                [1, 2, 0],
+                [3, 1, 0],
+                [0, 1, 2]];
+
+            return colorFunctions.rgba(vs[perm[i][0]] * 255,
+                vs[perm[i][1]] * 255,
+                vs[perm[i][2]] * 255,
+                a);
+        },
+
+        hue: function (color) {
+            return new(tree.Dimension)(color.toHSL().h);
+        },
+        saturation: function (color) {
+            return new(tree.Dimension)(color.toHSL().s * 100, '%');
+        },
+        lightness: function (color) {
+            return new(tree.Dimension)(color.toHSL().l * 100, '%');
+        },
+        hsvhue: function(color) {
+            return new(tree.Dimension)(color.toHSV().h);
+        },
+        hsvsaturation: function (color) {
+            return new(tree.Dimension)(color.toHSV().s * 100, '%');
+        },
+        hsvvalue: function (color) {
+            return new(tree.Dimension)(color.toHSV().v * 100, '%');
+        },
+        red: function (color) {
+            return new(tree.Dimension)(color.rgb[0]);
+        },
+        green: function (color) {
+            return new(tree.Dimension)(color.rgb[1]);
+        },
+        blue: function (color) {
+            return new(tree.Dimension)(color.rgb[2]);
+        },
+        alpha: function (color) {
+            return new(tree.Dimension)(color.toHSL().a);
+        },
+        luma: function (color) {
+            return new(tree.Dimension)(color.luma() * color.alpha * 100, '%');
+        },
+        luminance: function (color) {
+            var luminance =
+                (0.2126 * color.rgb[0] / 255)
+                    + (0.7152 * color.rgb[1] / 255)
+                    + (0.0722 * color.rgb[2] / 255);
+
+            return new(tree.Dimension)(luminance * color.alpha * 100, '%');
+        },
+        saturate: function (color, amount) {
+            // filter: saturate(3.2);
+            // should be kept as is, so check for color
+            if (!color.rgb) {
+                return null;
+            }
+            var hsl = color.toHSL();
+
+            hsl.s += amount.value / 100;
+            hsl.s = clamp(hsl.s);
+            return hsla(hsl);
+        },
+        desaturate: function (color, amount) {
+            var hsl = color.toHSL();
+
+            hsl.s -= amount.value / 100;
+            hsl.s = clamp(hsl.s);
+            return hsla(hsl);
+        },
+        lighten: function (color, amount) {
+            var hsl = color.toHSL();
+
+            hsl.l += amount.value / 100;
+            hsl.l = clamp(hsl.l);
+            return hsla(hsl);
+        },
+        darken: function (color, amount) {
+            var hsl = color.toHSL();
+
+            hsl.l -= amount.value / 100;
+            hsl.l = clamp(hsl.l);
+            return hsla(hsl);
+        },
+        fadein: function (color, amount) {
+            var hsl = color.toHSL();
+
+            hsl.a += amount.value / 100;
+            hsl.a = clamp(hsl.a);
+            return hsla(hsl);
+        },
+        fadeout: function (color, amount) {
+            var hsl = color.toHSL();
+
+            hsl.a -= amount.value / 100;
+            hsl.a = clamp(hsl.a);
+            return hsla(hsl);
+        },
+        fade: function (color, amount) {
+            var hsl = color.toHSL();
+
+            hsl.a = amount.value / 100;
+            hsl.a = clamp(hsl.a);
+            return hsla(hsl);
+        },
+        spin: function (color, amount) {
+            var hsl = color.toHSL();
+            var hue = (hsl.h + amount.value) % 360;
+
+            hsl.h = hue < 0 ? 360 + hue : hue;
+
+            return hsla(hsl);
+        },
+        //
+        // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
+        // http://sass-lang.com
+        //
+        mix: function (color1, color2, weight) {
+            if (!weight) {
+                weight = new(tree.Dimension)(50);
+            }
+            var p = weight.value / 100.0;
+            var w = p * 2 - 1;
+            var a = color1.toHSL().a - color2.toHSL().a;
+
+            var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
+            var w2 = 1 - w1;
+
+            var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2,
+                color1.rgb[1] * w1 + color2.rgb[1] * w2,
+                color1.rgb[2] * w1 + color2.rgb[2] * w2];
+
+            var alpha = color1.alpha * p + color2.alpha * (1 - p);
+
+            return new(tree.Color)(rgb, alpha);
+        },
+        greyscale: function (color) {
+            return colorFunctions.desaturate(color, new(tree.Dimension)(100));
+        },
+        contrast: function (color, dark, light, threshold) {
+            // filter: contrast(3.2);
+            // should be kept as is, so check for color
+            if (!color.rgb) {
+                return null;
+            }
+            if (typeof light === 'undefined') {
+                light = colorFunctions.rgba(255, 255, 255, 1.0);
+            }
+            if (typeof dark === 'undefined') {
+                dark = colorFunctions.rgba(0, 0, 0, 1.0);
+            }
+            //Figure out which is actually light and dark!
+            if (dark.luma() > light.luma()) {
+                var t = light;
+                light = dark;
+                dark = t;
+            }
+            if (typeof threshold === 'undefined') {
+                threshold = 0.43;
+            } else {
+                threshold = number(threshold);
+            }
+            if (color.luma() < threshold) {
+                return light;
+            } else {
+                return dark;
+            }
+        },
+        argb: function (color) {
+            return new(tree.Anonymous)(color.toARGB());
+        },
+        color: function(c) {
+            if ((c instanceof tree.Quoted) &&
+                (/^#([a-f0-9]{6}|[a-f0-9]{3})$/i.test(c.value))) {
+                return new(tree.Color)(c.value.slice(1));
+            }
+            if ((c instanceof tree.Color) || (c = tree.Color.fromKeyword(c.value))) {
+                c.keyword = undefined;
+                return c;
+            }
+            throw {
+                type:    "Argument",
+                message: "argument must be a color keyword or 3/6 digit hex e.g. #FFF"
+            };
+        },
+        tint: function(color, amount) {
+            return colorFunctions.mix(colorFunctions.rgb(255,255,255), color, amount);
+        },
+        shade: function(color, amount) {
+            return colorFunctions.mix(colorFunctions.rgb(0, 0, 0), color, amount);
+        }
+    };
+    functions.functionRegistry.addMultiple(colorFunctions);
+};
diff --git a/lib/less/functions/data-uri.js b/lib/less/functions/data-uri.js
new file mode 100644
index 0000000..c60be3a
--- /dev/null
+++ b/lib/less/functions/data-uri.js
@@ -0,0 +1,69 @@
+module.exports = function(functions, tree, less) {
+    functions.functionRegistry.add("data-uri", function(mimetypeNode, filePathNode) {
+
+        if (!less.environment.supportsDataURI(this.env)) {
+            return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
+        }
+
+        var mimetype = mimetypeNode.value;
+        var filePath = (filePathNode && filePathNode.value);
+
+        var useBase64 = false;
+
+        if (arguments.length < 2) {
+            filePath = mimetype;
+        }
+
+        var fragmentStart = filePath.indexOf('#');
+        var fragment = '';
+        if (fragmentStart!==-1) {
+            fragment = filePath.slice(fragmentStart);
+            filePath = filePath.slice(0, fragmentStart);
+        }
+
+        if (this.env.isPathRelative(filePath)) {
+            if (this.currentFileInfo.relativeUrls) {
+                filePath = less.environment.join(this.currentFileInfo.currentDirectory, filePath);
+            } else {
+                filePath = less.environment.join(this.currentFileInfo.entryPath, filePath);
+            }
+        }
+
+        // detect the mimetype if not given
+        if (arguments.length < 2) {
+
+            mimetype = less.environment.mimeLookup(this.env, filePath);
+
+            // use base 64 unless it's an ASCII or UTF-8 format
+            var charset = less.environment.charsetLookup(this.env, mimetype);
+            useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
+            if (useBase64) { mimetype += ';base64'; }
+        }
+        else {
+            useBase64 = /;base64$/.test(mimetype);
+        }
+
+        var buf = less.environment.readFileSync(filePath);
+
+        // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
+        // and the --ieCompat flag is enabled, return a normal url() instead.
+        var DATA_URI_MAX_KB = 32,
+            fileSizeInKB = parseInt((buf.length / 1024), 10);
+        if (fileSizeInKB >= DATA_URI_MAX_KB) {
+
+            if (this.env.ieCompat !== false) {
+                if (!this.env.silent) {
+                    console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
+                }
+
+                return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
+            }
+        }
+
+        buf = useBase64 ? buf.toString('base64')
+            : encodeURIComponent(buf);
+
+        var uri = "\"data:" + mimetype + ',' + buf + fragment + "\"";
+        return new(tree.URL)(new(tree.Anonymous)(uri));
+    });
+};
diff --git a/lib/less/functions/default.js b/lib/less/functions/default.js
new file mode 100644
index 0000000..6aa78aa
--- /dev/null
+++ b/lib/less/functions/default.js
@@ -0,0 +1,25 @@
+module.exports = function(functions, tree) {
+    var defaultFunc = {
+        eval: function () {
+            var v = this.value_, e = this.error_;
+            if (e) {
+                throw e;
+            }
+            if (v != null) {
+                return v ? tree.True : tree.False;
+            }
+        },
+        value: function (v) {
+            this.value_ = v;
+        },
+        error: function (e) {
+            this.error_ = e;
+        },
+        reset: function () {
+            this.value_ = this.error_ = null;
+        }
+    };
+
+    functions.functionRegistry.add("default", defaultFunc.eval.bind(defaultFunc));
+    tree.defaultFunc = defaultFunc;
+};
diff --git a/lib/less/functions/function-caller.js b/lib/less/functions/function-caller.js
new file mode 100644
index 0000000..da86d51
--- /dev/null
+++ b/lib/less/functions/function-caller.js
@@ -0,0 +1,15 @@
+module.exports = function(functions) {
+var functionCaller = function(name, env, currentFileInfo) {
+    this.name = name.toLowerCase();
+    this.function = functions.functionRegistry.get(this.name);
+    this.env = env;
+    this.currentFileInfo = currentFileInfo;
+};
+functionCaller.prototype.isValid = function() {
+    return Boolean(this.function);
+};
+functionCaller.prototype.call = function(args) {
+    return this.function.apply(this, args);
+};
+return functionCaller;
+};
diff --git a/lib/less/functions/function-registry.js b/lib/less/functions/function-registry.js
new file mode 100644
index 0000000..d39230d
--- /dev/null
+++ b/lib/less/functions/function-registry.js
@@ -0,0 +1,18 @@
+module.exports = {
+    _data: {},
+    add: function(name, func) {
+        if (this._data.hasOwnProperty(name)) {
+            //TODO warn
+        }
+        this._data[name] = func;
+    },
+    addMultiple: function(functions) {
+        Object.keys(functions).forEach(
+            function(name) {
+                this.add(name, functions[name]);
+            }.bind(this));
+    },
+    get: function(name) {
+        return this._data[name];
+    }
+};
diff --git a/lib/less/functions/index.js b/lib/less/functions/index.js
new file mode 100644
index 0000000..36d7167
--- /dev/null
+++ b/lib/less/functions/index.js
@@ -0,0 +1,18 @@
+module.exports = function(less, tree) {
+    var functions = {};
+    functions.functionRegistry = require("./function-registry.js");
+    functions.functionCaller = require("./function-caller.js")(functions);
+
+    //register functions
+    require("./color.js")(functions, tree);
+    require("./color-blending.js")(functions, tree);
+    require("./data-uri.js")(functions, tree, less);
+    require("./default.js")(functions, tree);
+    require("./math.js")(functions, tree);
+    require("./number.js")(functions, tree);
+    require("./string.js")(functions, tree);
+    require("./svg.js")(functions, tree, less);
+    require("./types.js")(functions, tree);
+
+    return functions;
+};
diff --git a/lib/less/functions/math.js b/lib/less/functions/math.js
new file mode 100644
index 0000000..4e56d8d
--- /dev/null
+++ b/lib/less/functions/math.js
@@ -0,0 +1,42 @@
+module.exports = function(functions, tree) {
+
+    var mathFunctions = {
+        // name,  unit
+        ceil:  null,
+        floor: null,
+        sqrt:  null,
+        abs:   null,
+        tan:   "",
+        sin:   "",
+        cos:   "",
+        atan:  "rad",
+        asin:  "rad",
+        acos:  "rad"
+    };
+
+    function _math(fn, unit, n) {
+        if (!(n instanceof tree.Dimension)) {
+            throw { type: "Argument", message: "argument must be a number" };
+        }
+        if (unit == null) {
+            unit = n.unit;
+        } else {
+            n = n.unify();
+        }
+        return new(tree.Dimension)(fn(parseFloat(n.value)), unit);
+    }
+
+    for (var f in mathFunctions) {
+        if (mathFunctions.hasOwnProperty(f)) {
+            mathFunctions[f] = _math.bind(null, Math[f], mathFunctions[f]);
+        }
+    }
+
+    mathFunctions.round = function (n, f) {
+        var fraction = typeof(f) === "undefined" ? 0 : f.value;
+        return _math(function(num) { return num.toFixed(fraction); }, null, n);
+    };
+
+    functions.functionRegistry.addMultiple(mathFunctions);
+
+};
diff --git a/lib/less/functions/number.js b/lib/less/functions/number.js
new file mode 100644
index 0000000..dc6fdfd
--- /dev/null
+++ b/lib/less/functions/number.js
@@ -0,0 +1,74 @@
+module.exports = function(functions, tree) {
+    var minMax = function (isMin, args) {
+        args = Array.prototype.slice.call(args);
+        switch(args.length) {
+            case 0: throw { type: "Argument", message: "one or more arguments required" };
+        }
+        var i, j, current, currentUnified, referenceUnified, unit, unitStatic, unitClone,
+            order  = [], // elems only contains original argument values.
+            values = {}; // key is the unit.toString() for unified tree.Dimension values,
+        // value is the index into the order array.
+        for (i = 0; i < args.length; i++) {
+            current = args[i];
+            if (!(current instanceof tree.Dimension)) {
+                if(Array.isArray(args[i].value)) {
+                    Array.prototype.push.apply(args, Array.prototype.slice.call(args[i].value));
+                }
+                continue;
+            }
+            currentUnified = current.unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(current.value, unitClone).unify() : current.unify();
+            unit = currentUnified.unit.toString() === "" && unitStatic !== undefined ? unitStatic : currentUnified.unit.toString();
+            unitStatic = unit !== "" && unitStatic === undefined || unit !== "" && order[0].unify().unit.toString() === "" ? unit : unitStatic;
+            unitClone = unit !== "" && unitClone === undefined ? current.unit.toString() : unitClone;
+            j = values[""] !== undefined && unit !== "" && unit === unitStatic ? values[""] : values[unit];
+            if (j === undefined) {
+                if(unitStatic !== undefined && unit !== unitStatic) {
+                    throw{ type: "Argument", message: "incompatible types" };
+                }
+                values[unit] = order.length;
+                order.push(current);
+                continue;
+            }
+            referenceUnified = order[j].unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(order[j].value, unitClone).unify() : order[j].unify();
+            if ( isMin && currentUnified.value < referenceUnified.value ||
+                !isMin && currentUnified.value > referenceUnified.value) {
+                order[j] = current;
+            }
+        }
+        if (order.length == 1) {
+            return order[0];
+        }
+        args = order.map(function (a) { return a.toCSS(this.env); }).join(this.env.compress ? "," : ", ");
+        return new(tree.Anonymous)((isMin ? "min" : "max") + "(" + args + ")");
+    };
+    functions.functionRegistry.addMultiple({
+        min: function () {
+            return minMax(true, arguments);
+        },
+        max: function () {
+            return minMax(false, arguments);
+        },
+        convert: function (val, unit) {
+            return val.convertTo(unit.value);
+        },
+        pi: function () {
+            return new(tree.Dimension)(Math.PI);
+        },
+        mod: function(a, b) {
+            return new(tree.Dimension)(a.value % b.value, a.unit);
+        },
+        pow: function(x, y) {
+            if (typeof x === "number" && typeof y === "number") {
+                x = new(tree.Dimension)(x);
+                y = new(tree.Dimension)(y);
+            } else if (!(x instanceof tree.Dimension) || !(y instanceof tree.Dimension)) {
+                throw { type: "Argument", message: "arguments must be numbers" };
+            }
+
+            return new(tree.Dimension)(Math.pow(x.value, y.value), x.unit);
+        },
+        percentage: function (n) {
+            return new(tree.Dimension)(n.value * 100, '%');
+        }
+    });
+};
diff --git a/lib/less/functions/string.js b/lib/less/functions/string.js
new file mode 100644
index 0000000..9851c89
--- /dev/null
+++ b/lib/less/functions/string.js
@@ -0,0 +1,30 @@
+module.exports = function(functions, tree) {
+    functions.functionRegistry.addMultiple({
+        e: function (str) {
+            return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str.value);
+        },
+        escape: function (str) {
+            return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
+        },
+        replace: function (string, pattern, replacement, flags) {
+            var result = string.value;
+
+            result = result.replace(new RegExp(pattern.value, flags ? flags.value : ''), replacement.value);
+            return new(tree.Quoted)(string.quote || '', result, string.escaped);
+        },
+        '%': function (string /* arg, arg, ...*/) {
+            var args = Array.prototype.slice.call(arguments, 1),
+                result = string.value;
+
+            for (var i = 0; i < args.length; i++) {
+                /*jshint loopfunc:true */
+                result = result.replace(/%[sda]/i, function(token) {
+                    var value = token.match(/s/i) ? args[i].value : args[i].toCSS();
+                    return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value;
+                });
+            }
+            result = result.replace(/%%/g, '%');
+            return new(tree.Quoted)(string.quote || '', result, string.escaped);
+        }
+    });
+};
diff --git a/lib/less/functions/svg.js b/lib/less/functions/svg.js
new file mode 100644
index 0000000..0cd71d5
--- /dev/null
+++ b/lib/less/functions/svg.js
@@ -0,0 +1,77 @@
+module.exports = function(functions, tree, less) {
+    functions.functionRegistry.add("svg-gradient", function(direction) {
+
+        function throwArgumentDescriptor() {
+            throw { type: "Argument", message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" };
+        }
+
+        if (arguments.length < 3) {
+            throwArgumentDescriptor();
+        }
+        var stops = Array.prototype.slice.call(arguments, 1),
+            gradientDirectionSvg,
+            gradientType = "linear",
+            rectangleDimension = 'x="0" y="0" width="1" height="1"',
+            useBase64 = true,
+            renderEnv = {compress: false},
+            returner,
+            directionValue = direction.toCSS(renderEnv),
+            i, color, position, positionValue, alpha;
+
+        switch (directionValue) {
+            case "to bottom":
+                gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
+                break;
+            case "to right":
+                gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
+                break;
+            case "to bottom right":
+                gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
+                break;
+            case "to top right":
+                gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
+                break;
+            case "ellipse":
+            case "ellipse at center":
+                gradientType = "radial";
+                gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
+                rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
+                break;
+            default:
+                throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" };
+        }
+        returner = '<?xml version="1.0" ?>' +
+            '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' +
+            '<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>';
+
+        for (i = 0; i < stops.length; i+= 1) {
+            if (stops[i].value) {
+                color = stops[i].value[0];
+                position = stops[i].value[1];
+            } else {
+                color = stops[i];
+                position = undefined;
+            }
+
+            if (!(color instanceof tree.Color) || (!((i === 0 || i+1 === stops.length) && position === undefined) && !(position instanceof tree.Dimension))) {
+                throwArgumentDescriptor();
+            }
+            positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%";
+            alpha = color.alpha;
+            returner += '<stop offset="' + positionValue + '" stop-color="' + color.toRGB() + '"' + (alpha < 1 ? ' stop-opacity="' + alpha + '"' : '') + '/>';
+        }
+        returner += '</' + gradientType + 'Gradient>' +
+            '<rect ' + rectangleDimension + ' fill="url(#gradient)" /></svg>';
+
+        if (useBase64) {
+            try {
+                returner = less.environment.encodeBase64(this.env, returner);
+            } catch(e) {
+                useBase64 = false;
+            }
+        }
+
+        returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'";
+        return new(tree.URL)(new(tree.Anonymous)(returner));
+    });
+};
diff --git a/lib/less/functions/types.js b/lib/less/functions/types.js
new file mode 100644
index 0000000..802d809
--- /dev/null
+++ b/lib/less/functions/types.js
@@ -0,0 +1,64 @@
+module.exports = function(functions, tree) {
+    var isa = function (n, Type) {
+        return (n instanceof Type) ? tree.True : tree.False;
+        },
+        isunit = function (n, unit) {
+            return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? tree.True : tree.False;
+        };
+    functions.functionRegistry.addMultiple({
+        iscolor: function (n) {
+            return isa(n, tree.Color);
+        },
+        isnumber: function (n) {
+            return isa(n, tree.Dimension);
+        },
+        isstring: function (n) {
+            return isa(n, tree.Quoted);
+        },
+        iskeyword: function (n) {
+            return isa(n, tree.Keyword);
+        },
+        isurl: function (n) {
+            return isa(n, tree.URL);
+        },
+        ispixel: function (n) {
+            return isunit(n, 'px');
+        },
+        ispercentage: function (n) {
+            return isunit(n, '%');
+        },
+        isem: function (n) {
+            return isunit(n, 'em');
+        },
+        isunit: isunit,
+        unit: function (val, unit) {
+            if(!(val instanceof tree.Dimension)) {
+                throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof tree.Operation ? ". Have you forgotten parenthesis?" : "") };
+            }
+            if (unit) {
+                if (unit instanceof tree.Keyword) {
+                    unit = unit.value;
+                } else {
+                    unit = unit.toCSS();
+                }
+            } else {
+                unit = "";
+            }
+            return new(tree.Dimension)(val.value, unit);
+        },
+        "get-unit": function (n) {
+            return new(tree.Anonymous)(n.unit);
+        },
+        extract: function(values, index) {
+            index = index.value - 1; // (1-based index)
+            // handle non-array values as an array of length 1
+            // return 'undefined' if index is invalid
+            return Array.isArray(values.value) ?
+                values.value[index] : Array(values)[index];
+        },
+        length: function(values) {
+            var n = Array.isArray(values.value) ? values.value.length : 1;
+            return new tree.Dimension(n);
+        }
+    });
+};
diff --git a/lib/less/non-node-index.js b/lib/less/non-node-index.js
index cacb207..c93846d 100644
--- a/lib/less/non-node-index.js
+++ b/lib/less/non-node-index.js
@@ -5,10 +5,10 @@ var less = {
     }
 };
 
-less.tree = (require('./tree'))(less);
+less.tree = (require('./tree'))(less, less.data);
 less.visitor = require('./visitor/index.js')(less, less.tree);
 less.Parser = (require('./parser'))(less, less.tree, less.visitor);
-less.tree.functions = (require('./functions'))(less, less.tree);
+less.functions = (require('./functions/index.js'))(less, less.tree);
 require('./env')(less.tree);
 
 less.tree.sourceMapOutput = require('./source-map-output.js')(less);
diff --git a/lib/less/tree.js b/lib/less/tree.js
index d771e3c..b3d7b1c 100644
--- a/lib/less/tree.js
+++ b/lib/less/tree.js
@@ -1,4 +1,4 @@
-module.exports = function (less) {
+module.exports = function (less, data) {
 
 var tree = {};
 
@@ -97,7 +97,7 @@ tree.outputRuleset = function (env, output, rules) {
 };
 
 tree.Alpha = require('./tree/alpha')(tree);
-tree.Color = require('./tree/color')(less.data, tree);
+tree.Color = require('./tree/color')(data, tree);
 tree.Directive = require('./tree/directive')(tree);
 tree.DetachedRuleset = require('./tree/detached-ruleset')(tree);
 tree.Operation = require('./tree/operation')(tree);
@@ -113,7 +113,7 @@ tree.Selector = require('./tree/selector')(tree);
 tree.Quoted = require('./tree/quoted')(tree);
 tree.Expression = require('./tree/expression')(tree);
 tree.Rule = require('./tree/rule')(tree);
-tree.Call = require('./tree/call')(tree);
+tree.Call = require('./tree/call')(tree, less);
 tree.URL = require('./tree/url')(tree);
 tree.Import = require('./tree/import')(tree);
 tree.mixin = {
@@ -133,6 +133,12 @@ tree.Negative = require('./tree/negative')(tree);
 tree.Extend = require('./tree/extend')(tree);
 tree.RulesetCall = require('./tree/ruleset-call')(tree);
 
+tree.fround = function(env, value) {
+    var precision = env && env.numPrecision;
+    //add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999....) are properly rounded...
+    return (precision == null) ? value : Number((value + 2e-16).toFixed(precision));
+};
+
 return tree;
 
 };
diff --git a/lib/less/tree/call.js b/lib/less/tree/call.js
index 951a083..08dc989 100644
--- a/lib/less/tree/call.js
+++ b/lib/less/tree/call.js
@@ -1,4 +1,4 @@
-module.exports = function (tree) {
+module.exports = function (tree, less) {
 
 //
 // A function call node.
@@ -18,7 +18,7 @@ Call.prototype = {
     },
     //
     // When evaluating a function call,
-    // we either find the function in `tree.functions` [1],
+    // we either find the function in `less.functions` [1],
     // in which case we call it, passing the  evaluated arguments,
     // if this returns null or we cannot find the function, we
     // simply print it out as it appeared originally [2].
@@ -31,13 +31,11 @@ Call.prototype = {
     //
     eval: function (env) {
         var args = this.args.map(function (a) { return a.eval(env); }),
-            nameLC = this.name.toLowerCase(),
-            result, func;
+            result, funcCaller = new less.functions.functionCaller(this.name, env, this.currentFileInfo);
 
-        if (nameLC in tree.functions) { // 1.
+        if (funcCaller.isValid()) { // 1.
             try {
-                func = new tree.functionCall(env, this.currentFileInfo);
-                result = func[nameLC].apply(func, args);
+                result = funcCaller.call(args);
                 if (result != null) {
                     return result;
                 }
diff --git a/test/less-test.js b/test/less-test.js
index 4bba05b..ad046d1 100644
--- a/test/less-test.js
+++ b/test/less-test.js
@@ -18,15 +18,17 @@ module.exports = function() {
         passedTests = 0;
 
 
-    less.tree.functions.add = function (a, b) {
-        return new(less.tree.Dimension)(a.value + b.value);
-    };
-    less.tree.functions.increment = function (a) {
-        return new(less.tree.Dimension)(a.value + 1);
-    };
-    less.tree.functions._color = function (str) {
-        if (str.value === "evil red") { return new(less.tree.Color)("600"); }
-    };
+    less.functions.functionRegistry.addMultiple({
+        add: function (a, b) {
+            return new(less.tree.Dimension)(a.value + b.value);
+        },
+        increment: function (a) {
+            return new(less.tree.Dimension)(a.value + 1);
+        },
+        _color: function (str) {
+            if (str.value === "evil red") { return new(less.tree.Color)("600"); }
+        }
+    });
 
     function testSourcemap(name, err, compiledLess, doReplacements, sourcemap) {
         fs.readFile(path.join('test/', name) + '.json', 'utf8', function (e, expectedSourcemap) {

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/less.js.git



More information about the Pkg-javascript-commits mailing list