2014-09-04 16:53:26 +00:00
|
|
|
// 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.
|
|
|
|
|
2014-09-05 16:56:36 +00:00
|
|
|
module.exports = (function() {
|
2014-09-04 16:53:26 +00:00
|
|
|
|
|
|
|
var types = {
|
|
|
|
gradients: [
|
|
|
|
'linear-gradient',
|
|
|
|
'radial-gradient',
|
|
|
|
'repeating-radial-gradient'
|
|
|
|
],
|
|
|
|
colors: [
|
|
|
|
'hex',
|
|
|
|
'rgb',
|
|
|
|
'rgba',
|
|
|
|
'hsl',
|
|
|
|
'literal'
|
2014-09-06 11:28:43 +00:00
|
|
|
],
|
|
|
|
metrics: [
|
|
|
|
'px'
|
2014-09-04 16:53:26 +00:00
|
|
|
]
|
|
|
|
};
|
|
|
|
|
2014-09-05 00:02:54 +00:00
|
|
|
var tokens = {
|
|
|
|
linearGradient: /^linear\-gradient/i,
|
2014-09-06 10:47:36 +00:00
|
|
|
radialGradient: /^radial\-gradient/i,
|
2014-09-05 00:02:54 +00:00
|
|
|
sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i,
|
2014-09-06 11:28:43 +00:00
|
|
|
pixelValue: /^([0-9]+)px/,
|
2014-09-06 13:25:10 +00:00
|
|
|
percentageValue: /^([0-9]+)\%/,
|
|
|
|
emValue: /^([0-9]+)em/,
|
2014-09-06 13:40:32 +00:00
|
|
|
angleValue: /^([0-9]+)deg/,
|
2014-09-05 00:02:54 +00:00
|
|
|
startCall: /^\(/,
|
|
|
|
endCall: /^\)/,
|
2014-09-06 13:53:38 +00:00
|
|
|
comma: /^,/,
|
|
|
|
hexColor: /^\#([0-9a-fA-F]+)/,
|
2014-09-06 15:24:00 +00:00
|
|
|
literalColor: /^([a-zA-Z]+)/,
|
|
|
|
rgbColor: /^rgb/i,
|
|
|
|
rgbaColor: /^rgba/i,
|
|
|
|
number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/
|
2014-09-05 00:02:54 +00:00
|
|
|
};
|
|
|
|
|
2014-09-05 16:56:36 +00:00
|
|
|
var input = '',
|
|
|
|
cursor = 0;
|
|
|
|
|
|
|
|
function error(msg) {
|
2014-09-05 08:28:40 +00:00
|
|
|
var err = new Error(input + ':' + cursor + ': ' + msg);
|
|
|
|
err.position = cursor;
|
2014-09-06 13:25:10 +00:00
|
|
|
//err.message = msg;
|
2014-09-05 08:28:40 +00:00
|
|
|
err.source = input;
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
2014-09-05 16:56:36 +00:00
|
|
|
function getAST() {
|
2014-09-06 10:33:19 +00:00
|
|
|
var ast = matchListDefinitions();
|
2014-09-05 08:28:40 +00:00
|
|
|
|
2014-09-05 16:56:36 +00:00
|
|
|
if (input.length > 0) {
|
2014-09-06 10:33:19 +00:00
|
|
|
error('Invalid input not EOF');
|
2014-09-05 08:28:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ast;
|
2014-09-06 10:33:19 +00:00
|
|
|
}
|
2014-09-04 16:53:26 +00:00
|
|
|
|
2014-09-06 10:33:19 +00:00
|
|
|
function matchListDefinitions() {
|
2014-09-05 00:02:54 +00:00
|
|
|
var definitions = [],
|
2014-09-06 10:33:19 +00:00
|
|
|
currentDefinition = matchDefinition();
|
2014-09-05 16:56:36 +00:00
|
|
|
|
|
|
|
if (currentDefinition) {
|
|
|
|
definitions.push(currentDefinition);
|
|
|
|
while (scan(tokens.comma)) {
|
2014-09-06 10:33:19 +00:00
|
|
|
currentDefinition = matchDefinition();
|
2014-09-05 16:56:36 +00:00
|
|
|
if (currentDefinition) {
|
|
|
|
definitions.push(currentDefinition);
|
2014-09-05 00:02:54 +00:00
|
|
|
} else {
|
2014-09-06 10:33:19 +00:00
|
|
|
error('One extra comma');
|
2014-09-04 16:53:26 +00:00
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return definitions;
|
2014-09-05 16:56:36 +00:00
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
|
2014-09-06 10:33:19 +00:00
|
|
|
function matchDefinition() {
|
2014-09-06 10:47:36 +00:00
|
|
|
return matchGradient(
|
|
|
|
'linear-gradient',
|
|
|
|
tokens.linearGradient,
|
|
|
|
matchOrientation);
|
2014-09-05 16:56:36 +00:00
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
|
2014-09-06 13:25:10 +00:00
|
|
|
function matchGradient(gradientType, pattern, orientationMatcher) {
|
2014-09-06 15:24:00 +00:00
|
|
|
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) {
|
2014-09-06 13:25:10 +00:00
|
|
|
var captures = scan(pattern),
|
2014-09-05 00:02:54 +00:00
|
|
|
orientation,
|
|
|
|
colorStops;
|
|
|
|
|
|
|
|
if (captures) {
|
2014-09-06 10:33:19 +00:00
|
|
|
if (!scan(tokens.startCall)) {
|
|
|
|
error('Missing (');
|
|
|
|
}
|
|
|
|
|
2014-09-06 15:24:00 +00:00
|
|
|
result = callback(captures);
|
2014-09-06 10:33:19 +00:00
|
|
|
|
|
|
|
if (!scan(tokens.endCall)) {
|
|
|
|
error('Missing )');
|
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
|
2014-09-06 15:24:00 +00:00
|
|
|
return result;
|
2014-09-05 00:02:54 +00:00
|
|
|
}
|
2014-09-05 16:56:36 +00:00
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
|
2014-09-05 16:56:36 +00:00
|
|
|
function matchOrientation() {
|
2014-09-06 13:40:32 +00:00
|
|
|
return matchSideOrCorner() ||
|
|
|
|
matchAngle();
|
2014-09-05 16:56:36 +00:00
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
|
2014-09-06 10:33:19 +00:00
|
|
|
function matchSideOrCorner() {
|
2014-09-05 16:56:36 +00:00
|
|
|
var captures = scan(tokens.sideOrCorner);
|
2014-09-05 00:02:54 +00:00
|
|
|
if (captures) {
|
|
|
|
return {
|
|
|
|
type: 'directional',
|
|
|
|
value: captures[1].toLowerCase()
|
|
|
|
};
|
|
|
|
}
|
2014-09-05 16:56:36 +00:00
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
|
2014-09-06 13:40:32 +00:00
|
|
|
function matchAngle() {
|
|
|
|
var captures = scan(tokens.angleValue);
|
|
|
|
if (captures) {
|
|
|
|
return {
|
|
|
|
type: 'angle',
|
|
|
|
value: captures[1]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-06 15:24:00 +00:00
|
|
|
function matchListing(matcher) {
|
|
|
|
var captures = matcher(),
|
|
|
|
result = [];
|
2014-09-05 00:02:54 +00:00
|
|
|
|
2014-09-06 15:24:00 +00:00
|
|
|
if (captures) {
|
|
|
|
result.push(captures);
|
2014-09-05 16:56:36 +00:00
|
|
|
while (scan(tokens.comma)) {
|
2014-09-06 15:24:00 +00:00
|
|
|
captures = matcher();
|
|
|
|
if (captures) {
|
|
|
|
result.push(captures);
|
2014-09-05 00:02:54 +00:00
|
|
|
} else {
|
2014-09-06 10:33:19 +00:00
|
|
|
error('One extra comma');
|
2014-09-04 16:53:26 +00:00
|
|
|
}
|
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
}
|
|
|
|
|
2014-09-06 15:24:00 +00:00
|
|
|
return result;
|
2014-09-05 16:56:36 +00:00
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
|
2014-09-06 10:33:19 +00:00
|
|
|
function matchColorStop() {
|
2014-09-06 11:28:43 +00:00
|
|
|
var color = matchColor();
|
|
|
|
|
|
|
|
if (!color) {
|
|
|
|
error('Expected color definition');
|
|
|
|
}
|
|
|
|
|
|
|
|
color.length = matchLength();
|
|
|
|
return color;
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchColor() {
|
2014-09-06 15:24:00 +00:00
|
|
|
return matchHexColor() ||
|
|
|
|
matchRGBAColor() ||
|
|
|
|
matchRGBColor() ||
|
|
|
|
matchLiteralColor();
|
2014-09-06 10:33:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function matchLiteralColor() {
|
2014-09-06 13:53:38 +00:00
|
|
|
var captures = scan(tokens.literalColor);
|
2014-09-06 10:33:19 +00:00
|
|
|
|
|
|
|
if (captures) {
|
|
|
|
return {
|
|
|
|
type: 'literal',
|
|
|
|
value: captures[0].toLowerCase()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-06 13:53:38 +00:00
|
|
|
function matchHexColor() {
|
|
|
|
var captures = scan(tokens.hexColor);
|
|
|
|
|
|
|
|
if (captures) {
|
|
|
|
return {
|
|
|
|
type: 'hex',
|
|
|
|
value: captures[1]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-06 15:24:00 +00:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2014-09-06 11:28:43 +00:00
|
|
|
function matchLength() {
|
2014-09-06 13:25:10 +00:00
|
|
|
return matchMetric(tokens.pixelValue, 'px') ||
|
|
|
|
matchMetric(tokens.percentageValue, '%') ||
|
|
|
|
matchMetric(tokens.emValue, 'em');
|
2014-09-06 11:28:43 +00:00
|
|
|
}
|
|
|
|
|
2014-09-06 13:25:10 +00:00
|
|
|
function matchMetric(pattern, metric) {
|
|
|
|
var captures = scan(pattern);
|
2014-09-06 11:28:43 +00:00
|
|
|
if (captures) {
|
|
|
|
return {
|
2014-09-06 13:25:10 +00:00
|
|
|
type: metric,
|
2014-09-06 11:28:43 +00:00
|
|
|
value: captures[1]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-05 16:56:36 +00:00
|
|
|
function scan(regexp) {
|
2014-09-05 00:02:54 +00:00
|
|
|
var captures,
|
|
|
|
blankCaptures;
|
|
|
|
|
2014-09-05 16:56:36 +00:00
|
|
|
blankCaptures = /^[\n\r\t\s]+/.exec(input);
|
2014-09-05 00:02:54 +00:00
|
|
|
if (blankCaptures) {
|
2014-09-05 16:56:36 +00:00
|
|
|
consume(blankCaptures[0].length);
|
2014-09-05 00:02:54 +00:00
|
|
|
}
|
|
|
|
|
2014-09-05 16:56:36 +00:00
|
|
|
captures = regexp.exec(input);
|
2014-09-05 00:02:54 +00:00
|
|
|
if (captures) {
|
2014-09-05 16:56:36 +00:00
|
|
|
consume(captures[0].length);
|
2014-09-05 00:02:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return captures;
|
2014-09-05 16:56:36 +00:00
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
|
2014-09-05 16:56:36 +00:00
|
|
|
function consume(size) {
|
|
|
|
cursor += size;
|
|
|
|
input = input.substr(size);
|
|
|
|
}
|
2014-09-05 00:02:54 +00:00
|
|
|
|
2014-09-05 16:56:36 +00:00
|
|
|
return function(code) {
|
|
|
|
input = code.toString();
|
|
|
|
return getAST();
|
|
|
|
};
|
2014-09-05 00:02:54 +00:00
|
|
|
})();
|