{
    var deg2rad = Math.PI / 180;

    /*
     ╔═        ═╗   ╔═        ═╗   ╔═     ═╗
     ║ al cl el ║   ║ ar cr er ║   ║ a c e ║
     ║ bl dl fl ║ * ║ br dr fr ║ = ║ b d f ║
     ║ 0  0  1  ║   ║ 0  0  1  ║   ║ 0 0 1 ║
     ╚═        ═╝   ╚═        ═╝   ╚═     ═╝
    */
    function multiply_matrices(l, r) {
        var al = l[0];
        var cl = l[1];
        var el = l[2];
        var bl = l[3];
        var dl = l[4];
        var fl = l[5];

        var ar = r[0];
        var cr = r[1];
        var er = r[2];
        var br = r[3];
        var dr = r[4];
        var fr = r[5];

        var a = al * ar + cl * br;
        var c = al * cr + cl * dr;
        var e = al * er + cl * fr + el;
        var b = bl * ar + dl * br;
        var d = bl * cr + dl * dr;
        var f = bl * er + dl * fr + fl;

        return [a, c, e, b, d, f];
    }
}

transformList
    = wsp* ts:transforms? wsp* { return ts; }

transforms
    = t:transform commaWsp* ts:transforms
    {
        return multiply_matrices(t, ts);
    }
    / t:transform

transform
    = matrix
    / translate
    / scale
    / rotate
    / skewX
    / skewY

matrix
    = "matrix" wsp* "(" wsp*
        a:number commaWsp
        b:number commaWsp
        c:number commaWsp
        d:number commaWsp
        e:number commaWsp
        f:number wsp* ")"
    {
        return [
            a, c, e,
            b, d, f
        ];
    }

translate
    = "translate" wsp* "(" wsp* tx:number ty:commaWspNumber? wsp* ")"
    {
        return [
            1, 0, tx,
            0, 1, ty || 0
        ];
    }

scale
    = "scale" wsp* "(" wsp* sx:number sy:commaWspNumber? wsp* ")"
    {
        return [
            sx, 0,                     0,
            0,  sy === null ? sx : sy, 0
        ];
    }

rotate
    = "rotate" wsp* "(" wsp* angle:number c:commaWspTwoNumbers? wsp* ")"
    {
        var cos = Math.cos(deg2rad * angle);
        var sin = Math.sin(deg2rad * angle);
        if (c !== null) {
            var x = c[0];
            var y = c[1];
            return [
                cos, -sin, cos * -x + -sin * -y + x,
                sin,  cos, sin * -x +  cos * -y + y
            ];
        }
        return [
            cos, -sin, 0,
            sin,  cos, 0
        ];
    }

skewX
    = "skewX" wsp* "(" wsp* angle:number wsp* ")"
    {
        return [
            1, Math.tan(deg2rad * angle), 0,
            0, 1,                         0
        ];
    }

skewY
    = "skewY" wsp* "(" wsp* angle:number wsp* ")"
    {
        return [
            1,                         0, 0,
            Math.tan(deg2rad * angle), 1, 0
        ];
    }

number
    = f:(sign? floatingPointConstant) { return parseFloat(f.join("")); }
    / i:(sign? integerConstant) { return parseInt(i.join("")); }

commaWspNumber
    = commaWsp n:number { return n; }

commaWspTwoNumbers
    = commaWsp n1:number commaWsp n2:number { return [n1, n2]; }

commaWsp
    = (wsp+ comma? wsp*) / (comma wsp*)

comma
    = ","

integerConstant
    = ds:digitSequence { return ds.join(""); }

floatingPointConstant
    = f:(fractionalConstant exponent?) { return f.join(""); }
    / d:(digitSequence exponent) { return d.join(""); }

fractionalConstant "fractionalConstant"
    = d1:digitSequence? "." d2:digitSequence { return [d1 ? d1.join("") : null, ".", d2.join("")].join(""); }
    / d:digitSequence "." { return d.join(""); }

exponent
    =  e:([eE] sign? digitSequence) { return [e[0], e[1], e[2].join("")].join(""); }

sign
    = [+-]

digitSequence
    = digit+

digit
    = [0-9]

wsp
    = [\u0020\u0009\u000D\u000A]
