gradient-parser/gradient-parser.js
Rafael Caricio fde01a6cf1 DRY
2014-09-06 18:13:10 +02:00

232 lines
4.6 KiB
JavaScript

// Copyright (c) 2014 Rafael Caricio. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module.exports = (function() {
var types = {
gradients: [
'linear-gradient',
'radial-gradient',
'repeating-radial-gradient'
],
colors: [
'hex',
'rgb',
'rgba',
'hsl',
'literal'
],
metrics: [
'px'
]
};
var tokens = {
linearGradient: /^linear\-gradient/i,
radialGradient: /^radial\-gradient/i,
sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i,
pixelValue: /^([0-9]+)px/,
percentageValue: /^([0-9]+)\%/,
emValue: /^([0-9]+)em/,
angleValue: /^([0-9]+)deg/,
startCall: /^\(/,
endCall: /^\)/,
comma: /^,/,
hexColor: /^\#([0-9a-fA-F]+)/,
literalColor: /^([a-zA-Z]+)/,
rgbColor: /^rgb/i,
rgbaColor: /^rgba/i,
number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/
};
var input = '';
function error(msg) {
var err = new Error(input + ': ' + msg);
err.source = input;
throw err;
}
function getAST() {
var ast = matchListDefinitions();
if (input.length > 0) {
error('Invalid input not EOF');
}
return ast;
}
function matchListDefinitions() {
return matchListing(matchDefinition);
}
function matchDefinition() {
return matchGradient(
'linear-gradient',
tokens.linearGradient,
matchOrientation);
}
function matchGradient(gradientType, pattern, orientationMatcher) {
return matchCall(pattern, function(captures) {
orientation = orientationMatcher();
if (orientation) {
if (!scan(tokens.comma)) {
error('Missing comma before color stops');
}
}
colorStops = matchListing(matchColorStop);
return {
type: gradientType,
orientation: orientation,
colorStops: colorStops
};
});
}
function matchCall(pattern, callback) {
var captures = scan(pattern),
orientation,
colorStops;
if (captures) {
if (!scan(tokens.startCall)) {
error('Missing (');
}
result = callback(captures);
if (!scan(tokens.endCall)) {
error('Missing )');
}
return result;
}
}
function matchOrientation() {
return matchSideOrCorner() ||
matchAngle();
}
function matchSideOrCorner() {
return match('directional', tokens.sideOrCorner, 1);
}
function matchAngle() {
return match('angular', tokens.angleValue, 1);
}
function matchListing(matcher) {
var captures = matcher(),
result = [];
if (captures) {
result.push(captures);
while (scan(tokens.comma)) {
captures = matcher();
if (captures) {
result.push(captures);
} else {
error('One extra comma');
}
}
}
return result;
}
function matchColorStop() {
var color = matchColor();
if (!color) {
error('Expected color definition');
}
color.length = matchLength();
return color;
}
function matchColor() {
return matchHexColor() ||
matchRGBAColor() ||
matchRGBColor() ||
matchLiteralColor();
}
function matchLiteralColor() {
return match('literal', tokens.literalColor, 0);
}
function matchHexColor() {
return match('hex', tokens.hexColor, 1);
}
function matchRGBColor() {
return matchCall(tokens.rgbColor, function() {
return {
type: 'rgb',
value: matchListing(matchNumber)
};
});
}
function matchRGBAColor() {
return matchCall(tokens.rgbaColor, function() {
return {
type: 'rgba',
value: matchListing(matchNumber)
};
});
}
function matchNumber() {
return scan(tokens.number)[1];
}
function matchLength() {
return match('px', tokens.pixelValue, 1) ||
match('%', tokens.percentageValue, 1) ||
match('em', tokens.emValue, 1);
}
function match(type, pattern, captureIndex) {
var captures = scan(pattern);
if (captures) {
return {
type: type,
value: captures[captureIndex]
};
}
}
function scan(regexp) {
var captures,
blankCaptures;
blankCaptures = /^[\n\r\t\s]+/.exec(input);
if (blankCaptures) {
consume(blankCaptures[0].length);
}
captures = regexp.exec(input);
if (captures) {
consume(captures[0].length);
}
return captures;
}
function consume(size) {
input = input.substr(size);
}
return function(code) {
input = code.toString();
return getAST();
};
})();