install gulp
This commit is contained in:
+129
@@ -0,0 +1,129 @@
|
||||
var TokenType = require('../token-types');
|
||||
|
||||
/**
|
||||
* Mark whitespaces and comments
|
||||
* @param {Array} tokens
|
||||
*/
|
||||
function markSpacesAndComments(tokens) {
|
||||
var tokensLength = tokens.length;
|
||||
var lastSpaceIndex = -1;
|
||||
var lastSpaceOrCommentIndex = -1;
|
||||
var spaces = [-1, -1];
|
||||
var token; // current token
|
||||
var type; // current token's type
|
||||
|
||||
// For every token in the token list, mark spaces and line breaks
|
||||
// as spaces (set both `ws` and `sc` flags). Mark multiline comments
|
||||
// with `sc` flag.
|
||||
// If there are several spaces or tabs or line breaks or multiline
|
||||
// comments in a row, group them: take the last one's index number
|
||||
// and save it to the first token in the group as a reference
|
||||
// (e.g., `ws_last = 7` for a group of whitespaces or `sc_last = 9`
|
||||
// for a group of whitespaces and comments):
|
||||
for (var i = 0; i < tokensLength; i++) {
|
||||
type = tokens[i].type;
|
||||
|
||||
if (type === TokenType.Space ||
|
||||
type === TokenType.Tab ||
|
||||
type === TokenType.Newline) {
|
||||
markSpace(tokens, i, spaces);
|
||||
} else if (type === TokenType.CommentML) {
|
||||
markComment(tokens, i, spaces);
|
||||
} else {
|
||||
markEndOfSpacesAndComments(tokens, i, spaces);
|
||||
}
|
||||
}
|
||||
|
||||
markEndOfSpacesAndComments(tokens, i, spaces);
|
||||
}
|
||||
|
||||
function markSpace(tokens, i, spaces) {
|
||||
var token = tokens[i];
|
||||
token.ws = true;
|
||||
token.sc = true;
|
||||
|
||||
if (spaces[0] === -1) spaces[0] = i;
|
||||
if (spaces[1] === -1) spaces[1] = i;
|
||||
}
|
||||
|
||||
function markComment(tokens, i, spaces) {
|
||||
var ws = spaces[0];
|
||||
tokens[i].sc = true;
|
||||
|
||||
if (ws !== -1) {
|
||||
tokens[ws].ws_last = i - 1;
|
||||
spaces[0] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
function markEndOfSpacesAndComments(tokens, i, spaces) {
|
||||
var ws = spaces[0];
|
||||
var sc = spaces[1];
|
||||
if (ws !== -1) {
|
||||
tokens[ws].ws_last = i - 1;
|
||||
spaces[0] = -1;
|
||||
}
|
||||
if (sc !== -1) {
|
||||
tokens[sc].sc_last = i - 1;
|
||||
spaces[1] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pair brackets
|
||||
* @param {Array} tokens
|
||||
*/
|
||||
function markBrackets(tokens) {
|
||||
var tokensLength = tokens.length;
|
||||
var ps = [], // parenthesis
|
||||
sbs = [], // square brackets
|
||||
cbs = [], // curly brackets
|
||||
t; // current token
|
||||
|
||||
// For every token in the token list, if we meet an opening (left)
|
||||
// bracket, push its index number to a corresponding array.
|
||||
// If we then meet a closing (right) bracket, look at the corresponding
|
||||
// array. If there are any elements (records about previously met
|
||||
// left brackets), take a token of the last left bracket (take
|
||||
// the last index number from the array and find a token with
|
||||
// this index number) and save right bracket's index as a reference:
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
t = tokens[i];
|
||||
var type = t.type;
|
||||
|
||||
if (type === TokenType.LeftParenthesis) {
|
||||
ps.push(i);
|
||||
} else if (type === TokenType.RightParenthesis) {
|
||||
if (ps.length) {
|
||||
t.left = ps.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
} else if (type === TokenType.LeftSquareBracket) {
|
||||
sbs.push(i);
|
||||
} else if (type === TokenType.RightSquareBracket) {
|
||||
if (sbs.length) {
|
||||
t.left = sbs.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
} else if (type === TokenType.LeftCurlyBracket) {
|
||||
cbs.push(i);
|
||||
} else if (type === TokenType.RightCurlyBracket) {
|
||||
if (cbs.length) {
|
||||
t.left = cbs.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} tokens
|
||||
*/
|
||||
function markTokens(tokens) {
|
||||
// Mark paired brackets:
|
||||
markBrackets(tokens);
|
||||
// Mark whitespaces and comments:
|
||||
markSpacesAndComments(tokens);
|
||||
}
|
||||
|
||||
module.exports = markTokens;
|
||||
+2688
File diff suppressed because it is too large
Load Diff
+80
@@ -0,0 +1,80 @@
|
||||
module.exports = function stringify(tree) {
|
||||
// TODO: Better error message
|
||||
if (!tree) throw new Error('We need tree to translate');
|
||||
|
||||
function _t(tree) {
|
||||
var type = tree.type;
|
||||
if (_unique[type]) return _unique[type](tree);
|
||||
if (typeof tree.content === 'string') return tree.content;
|
||||
if (Array.isArray(tree.content)) return _composite(tree.content);
|
||||
return '';
|
||||
}
|
||||
|
||||
function _composite(t, i) {
|
||||
if (!t) return '';
|
||||
|
||||
var s = '';
|
||||
i = i || 0;
|
||||
for (; i < t.length; i++) s += _t(t[i]);
|
||||
return s;
|
||||
}
|
||||
|
||||
var _unique = {
|
||||
'arguments': function(t) {
|
||||
return '(' + _composite(t.content) + ')';
|
||||
},
|
||||
'atkeyword': function(t) {
|
||||
return '@' + _composite(t.content);
|
||||
},
|
||||
'atruler': function(t) {
|
||||
return _t(t.content[0]) + _t(t.content[1]) + '{' + _t(t.content[2]) + '}';
|
||||
},
|
||||
'attribute': function(t) {
|
||||
return '[' + _composite(t.content) + ']';
|
||||
},
|
||||
'block': function(t) {
|
||||
return '{' + _composite(t.content) + '}';
|
||||
},
|
||||
'braces': function(t) {
|
||||
return t.content[0] + _composite(t.content.slice(2)) + t.content[1];
|
||||
},
|
||||
'class': function(t) {
|
||||
return '.' + _composite(t.content);
|
||||
},
|
||||
'color': function(t) {
|
||||
return '#' + t.content;
|
||||
},
|
||||
'filter': function(t) {
|
||||
return _t(t.content[0]) + ':' + _t(t.content[1]);
|
||||
},
|
||||
'functionExpression': function(t) {
|
||||
return 'expression(' + t.content + ')';
|
||||
},
|
||||
'id': function (t) {
|
||||
return '#' + t.content;
|
||||
},
|
||||
'important': function(t) {
|
||||
return '!' + _composite(t.content) + 'important';
|
||||
},
|
||||
'multilineComment': function(t) {
|
||||
return '/*' + t.content + '*/';
|
||||
},
|
||||
'nthSelector': function(t) {
|
||||
return ':' + _t(t.content[0]) + '(' + _composite(t.content.slice(1)) + ')';
|
||||
},
|
||||
'percentage': function(t) {
|
||||
return _composite(t.content) + '%';
|
||||
},
|
||||
'pseudoClass': function(t) {
|
||||
return ':' + _composite(t.content);
|
||||
},
|
||||
'pseudoElement': function(t) {
|
||||
return '::' + _composite(t.content);
|
||||
},
|
||||
'uri': function(t) {
|
||||
return 'url(' + _composite(t.content) + ')';
|
||||
}
|
||||
};
|
||||
|
||||
return _t(tree);
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
module.exports = function(css) {
|
||||
var TokenType = require('../token-types');
|
||||
|
||||
var tokens = [],
|
||||
urlMode = false,
|
||||
blockMode = 0,
|
||||
pos = 0,
|
||||
tn = 0,
|
||||
ln = 1,
|
||||
col = 1;
|
||||
var cssLength = 0;
|
||||
|
||||
var Punctuation = {
|
||||
' ': TokenType.Space,
|
||||
'\n': TokenType.Newline,
|
||||
'\r': TokenType.Newline,
|
||||
'\t': TokenType.Tab,
|
||||
'!': TokenType.ExclamationMark,
|
||||
'"': TokenType.QuotationMark,
|
||||
'#': TokenType.NumberSign,
|
||||
'$': TokenType.DollarSign,
|
||||
'%': TokenType.PercentSign,
|
||||
'&': TokenType.Ampersand,
|
||||
'\'': TokenType.Apostrophe,
|
||||
'(': TokenType.LeftParenthesis,
|
||||
')': TokenType.RightParenthesis,
|
||||
'*': TokenType.Asterisk,
|
||||
'+': TokenType.PlusSign,
|
||||
',': TokenType.Comma,
|
||||
'-': TokenType.HyphenMinus,
|
||||
'.': TokenType.FullStop,
|
||||
'/': TokenType.Solidus,
|
||||
':': TokenType.Colon,
|
||||
';': TokenType.Semicolon,
|
||||
'<': TokenType.LessThanSign,
|
||||
'=': TokenType.EqualsSign,
|
||||
'>': TokenType.GreaterThanSign,
|
||||
'?': TokenType.QuestionMark,
|
||||
'@': TokenType.CommercialAt,
|
||||
'[': TokenType.LeftSquareBracket,
|
||||
']': TokenType.RightSquareBracket,
|
||||
'^': TokenType.CircumflexAccent,
|
||||
'_': TokenType.LowLine,
|
||||
'{': TokenType.LeftCurlyBracket,
|
||||
'|': TokenType.VerticalLine,
|
||||
'}': TokenType.RightCurlyBracket,
|
||||
'~': TokenType.Tilde
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a token to the token list
|
||||
* @param {string} type
|
||||
* @param {string} value
|
||||
*/
|
||||
function pushToken(type, value, column) {
|
||||
tokens.push({
|
||||
tn: tn++,
|
||||
ln: ln,
|
||||
col: column,
|
||||
type: type,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character is a decimal digit
|
||||
* @param {string} c Character
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isDecimalDigit(c) {
|
||||
return '0123456789'.indexOf(c) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse spaces
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseSpaces(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a non-space character:
|
||||
for (; pos < cssLength; pos++) {
|
||||
if (css.charAt(pos) !== ' ') break;
|
||||
}
|
||||
|
||||
// Add a substring containing only spaces to tokens:
|
||||
pushToken(TokenType.Space, css.substring(start, pos--), col);
|
||||
col += (pos - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string within quotes
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
* @param {string} q Quote (either `'` or `"`)
|
||||
*/
|
||||
function parseString(css, q) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a matching quote:
|
||||
for (pos++; pos < cssLength; pos++) {
|
||||
// Skip escaped quotes:
|
||||
if (css.charAt(pos) === '\\') pos++;
|
||||
else if (css.charAt(pos) === q) break;
|
||||
}
|
||||
|
||||
// Add the string (including quotes) to tokens:
|
||||
pushToken(q === '"' ? TokenType.StringDQ : TokenType.StringSQ, css.substring(start, pos + 1), col);
|
||||
col += (pos - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse numbers
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseDecimalNumber(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a character that's not a digit:
|
||||
for (; pos < cssLength; pos++) {
|
||||
if (!isDecimalDigit(css.charAt(pos))) break;
|
||||
}
|
||||
|
||||
// Add the number to tokens:
|
||||
pushToken(TokenType.DecimalNumber, css.substring(start, pos--), col);
|
||||
col += (pos - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse identifier
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseIdentifier(css) {
|
||||
var start = pos;
|
||||
|
||||
// Skip all opening slashes:
|
||||
while (css.charAt(pos) === '/') pos++;
|
||||
|
||||
// Read the string until we meet a punctuation mark:
|
||||
for (; pos < cssLength; pos++) {
|
||||
// Skip all '\':
|
||||
if (css.charAt(pos) === '\\') pos++;
|
||||
else if (Punctuation[css.charAt(pos)]) break;
|
||||
}
|
||||
|
||||
var ident = css.substring(start, pos--);
|
||||
|
||||
// Enter url mode if parsed substring is `url`:
|
||||
urlMode = urlMode || ident === 'url';
|
||||
|
||||
// Add identifier to tokens:
|
||||
pushToken(TokenType.Identifier, ident, col);
|
||||
col += (pos - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a multiline comment
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseMLComment(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet `*/`.
|
||||
// Since we already know first 2 characters (`/*`), start reading
|
||||
// from `pos + 2`:
|
||||
for (pos = pos + 2; pos < cssLength; pos++) {
|
||||
if (css.charAt(pos) === '*' && css.charAt(pos + 1) === '/') {
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add full comment (including `/*` and `*/`) to the list of tokens:
|
||||
var comment = css.substring(start, pos + 1);
|
||||
pushToken(TokenType.CommentML, comment, col);
|
||||
|
||||
var newlines = comment.split('\n');
|
||||
if (newlines.length > 1) {
|
||||
ln += newlines.length - 1;
|
||||
col = newlines[newlines.length - 1].length;
|
||||
} else {
|
||||
col += (pos - start);
|
||||
}
|
||||
}
|
||||
|
||||
function parseSLComment(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet line break.
|
||||
// Since we already know first 2 characters (`//`), start reading
|
||||
// from `pos + 2`:
|
||||
for (pos+=2; pos < cssLength; pos++) {
|
||||
if (css.charAt(pos) === '\n' || css.charAt(pos) === '\r') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add comment (including `//` and line break) to the list of tokens:
|
||||
pushToken(TokenType.CommentSL, css.substring(start, pos--), col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a CSS string to a list of tokens
|
||||
* @param {string} css CSS string
|
||||
* @returns {Array} List of tokens
|
||||
* @private
|
||||
*/
|
||||
function getTokens(css) {
|
||||
var c; // current character
|
||||
var cn; // next character
|
||||
|
||||
cssLength = css.length;
|
||||
|
||||
// Parse string, character by character:
|
||||
for (pos = 0; pos < cssLength; col++, pos++) {
|
||||
c = css.charAt(pos);
|
||||
cn = css.charAt(pos + 1);
|
||||
|
||||
// If we meet `/*`, it's a start of a multiline comment.
|
||||
// Parse following characters as a multiline comment:
|
||||
if (c === '/' && cn === '*') {
|
||||
parseMLComment(css);
|
||||
}
|
||||
|
||||
// If we meet `//` and it is not a part of url:
|
||||
else if (!urlMode && c === '/' && cn === '/') {
|
||||
// If we're currently inside a block, treat `//` as a start
|
||||
// of identifier. Else treat `//` as a start of a single-line
|
||||
// comment:
|
||||
if (blockMode > 0) parseIdentifier(css);
|
||||
else parseSLComment(css);
|
||||
}
|
||||
|
||||
// If current character is a double or single quote, it's a start
|
||||
// of a string:
|
||||
else if (c === '"' || c === "'") {
|
||||
parseString(css, c);
|
||||
}
|
||||
|
||||
// If current character is a space:
|
||||
else if (c === ' ') {
|
||||
parseSpaces(css);
|
||||
}
|
||||
|
||||
// If current character is a punctuation mark:
|
||||
else if (Punctuation[c]) {
|
||||
// Add it to the list of tokens:
|
||||
pushToken(Punctuation[c], c, col);
|
||||
if (c === '\n' || c === '\r') {
|
||||
ln++;
|
||||
col = 0;
|
||||
} // Go to next line
|
||||
else if (c === ')') urlMode = false; // exit url mode
|
||||
else if (c === '{') blockMode++; // enter a block
|
||||
else if (c === '}') blockMode--; // exit a block
|
||||
}
|
||||
|
||||
// If current character is a decimal digit:
|
||||
else if (isDecimalDigit(c)) {
|
||||
parseDecimalNumber(css);
|
||||
}
|
||||
|
||||
// If current character is anything else:
|
||||
else {
|
||||
parseIdentifier(css);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
return getTokens(css);
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
var Node = require('./node');
|
||||
var parse = require('./parse');
|
||||
|
||||
module.exports = {
|
||||
createNode: function(options) {
|
||||
return new Node(options.type, options.content);
|
||||
},
|
||||
parse: parse
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
var TokenType = require('../token-types');
|
||||
|
||||
module.exports = (function() {
|
||||
/**
|
||||
* Mark whitespaces and comments
|
||||
*/
|
||||
function markSC(tokens) {
|
||||
var tokensLength = tokens.length,
|
||||
ws = -1, // flag for whitespaces
|
||||
sc = -1, // flag for whitespaces and comments
|
||||
t; // current token
|
||||
|
||||
// For every token in the token list, mark spaces and line breaks
|
||||
// as spaces (set both `ws` and `sc` flags). Mark multiline comments
|
||||
// with `sc` flag.
|
||||
// If there are several spaces or tabs or line breaks or multiline
|
||||
// comments in a row, group them: take the last one's index number
|
||||
// and save it to the first token in the group as a reference
|
||||
// (e.g., `ws_last = 7` for a group of whitespaces or `sc_last = 9`
|
||||
// for a group of whitespaces and comments):
|
||||
for (var i = 0; i < tokensLength; i++) {
|
||||
t = tokens[i];
|
||||
switch (t.type) {
|
||||
case TokenType.Space:
|
||||
case TokenType.Tab:
|
||||
case TokenType.Newline:
|
||||
t.ws = true;
|
||||
t.sc = true;
|
||||
|
||||
if (ws === -1) ws = i;
|
||||
if (sc === -1) sc = i;
|
||||
|
||||
break;
|
||||
case TokenType.CommentML:
|
||||
case TokenType.CommentSL:
|
||||
if (ws !== -1) {
|
||||
tokens[ws].ws_last = i - 1;
|
||||
ws = -1;
|
||||
}
|
||||
|
||||
t.sc = true;
|
||||
|
||||
break;
|
||||
default:
|
||||
if (ws !== -1) {
|
||||
tokens[ws].ws_last = i - 1;
|
||||
ws = -1;
|
||||
}
|
||||
|
||||
if (sc !== -1) {
|
||||
tokens[sc].sc_last = i - 1;
|
||||
sc = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ws !== -1) tokens[ws].ws_last = i - 1;
|
||||
if (sc !== -1) tokens[sc].sc_last = i - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pair brackets
|
||||
*/
|
||||
function markBrackets(tokens) {
|
||||
var tokensLength = tokens.length;
|
||||
var ps = [], // parenthesis
|
||||
sbs = [], // square brackets
|
||||
cbs = [], // curly brackets
|
||||
t; // current token
|
||||
|
||||
// For every token in the token list, if we meet an opening (left)
|
||||
// bracket, push its index number to a corresponding array.
|
||||
// If we then meet a closing (right) bracket, look at the corresponding
|
||||
// array. If there are any elements (records about previously met
|
||||
// left brackets), take a token of the last left bracket (take
|
||||
// the last index number from the array and find a token with
|
||||
// this index number) and save right bracket's index as a reference:
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
t = tokens[i];
|
||||
switch(t.type) {
|
||||
case TokenType.LeftParenthesis:
|
||||
ps.push(i);
|
||||
break;
|
||||
case TokenType.RightParenthesis:
|
||||
if (ps.length) {
|
||||
t.left = ps.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
break;
|
||||
case TokenType.LeftSquareBracket:
|
||||
sbs.push(i);
|
||||
break;
|
||||
case TokenType.RightSquareBracket:
|
||||
if (sbs.length) {
|
||||
t.left = sbs.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
break;
|
||||
case TokenType.LeftCurlyBracket:
|
||||
cbs.push(i);
|
||||
break;
|
||||
case TokenType.RightCurlyBracket:
|
||||
if (cbs.length) {
|
||||
t.left = cbs.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return function(tokens) {
|
||||
markBrackets(tokens);
|
||||
markSC(tokens);
|
||||
}
|
||||
})();
|
||||
+3168
File diff suppressed because it is too large
Load Diff
+95
@@ -0,0 +1,95 @@
|
||||
module.exports = function stringify(tree) {
|
||||
// TODO: Better error message
|
||||
if (!tree) throw new Error('We need tree to translate');
|
||||
|
||||
function _t(tree) {
|
||||
var type = tree.type;
|
||||
if (_unique[type]) return _unique[type](tree);
|
||||
if (typeof tree.content === 'string') return tree.content;
|
||||
if (Array.isArray(tree.content)) return _composite(tree.content);
|
||||
return '';
|
||||
}
|
||||
|
||||
function _composite(t, i) {
|
||||
if (!t) return '';
|
||||
|
||||
var s = '';
|
||||
i = i || 0;
|
||||
for (; i < t.length; i++) s += _t(t[i]);
|
||||
return s;
|
||||
}
|
||||
|
||||
var _unique = {
|
||||
'arguments': function(t) {
|
||||
return '(' + _composite(t.content) + ')';
|
||||
},
|
||||
'atkeyword': function(t) {
|
||||
return '@' + _composite(t.content);
|
||||
},
|
||||
'atruler': function(t) {
|
||||
return _t(t.content[0]) + _t(t.content[1]) + '{' + _t(t.content[2]) + '}';
|
||||
},
|
||||
'attribute': function(t) {
|
||||
return '[' + _composite(t.content) + ']';
|
||||
},
|
||||
'block': function(t) {
|
||||
return '{' + _composite(t.content) + '}';
|
||||
},
|
||||
'braces': function(t) {
|
||||
return t.content[0] + _composite(t.content.slice(2)) + t.content[1];
|
||||
},
|
||||
'class': function(t) {
|
||||
return '.' + _composite(t.content);
|
||||
},
|
||||
'color': function(t) {
|
||||
return '#' + t.content;
|
||||
},
|
||||
'escapedString': function(t) {
|
||||
return '~' + t.content;
|
||||
},
|
||||
'filter': function(t) {
|
||||
return _t(t.content[0]) + ':' + _t(t.content[1]);
|
||||
},
|
||||
'functionExpression': function(t) {
|
||||
return 'expression(' + t.content + ')';
|
||||
},
|
||||
'id': function (t) {
|
||||
return '#' + t.content;
|
||||
},
|
||||
'important': function(t) {
|
||||
return '!' + _composite(t.content) + 'important';
|
||||
},
|
||||
'interpolatedVariable': function(t) {
|
||||
return '@{' + _composite(t.content) + '}';
|
||||
},
|
||||
'multilineComment': function (t) {
|
||||
return '/*' + t.content + '*/';
|
||||
},
|
||||
'nthSelector': function(t) {
|
||||
return ':' + _t(t.content[0]) + '(' + _composite(t.content.slice(1)) + ')';
|
||||
},
|
||||
'percentage': function(t) {
|
||||
return _t(t.content) + '%';
|
||||
},
|
||||
'pseudoClass': function(t) {
|
||||
return ':' + _composite(t.content);
|
||||
},
|
||||
'pseudoElement': function(t) {
|
||||
return '::' + _composite(t.content);
|
||||
},
|
||||
'singlelineComment': function (t) {
|
||||
return '/' + '/' + t.content;
|
||||
},
|
||||
'uri': function(t) {
|
||||
return 'url(' + _composite(t.content) + ')';
|
||||
},
|
||||
'variable': function(t) {
|
||||
return '@' + _composite(t.content);
|
||||
},
|
||||
'variablesList': function(t) {
|
||||
return _composite(t.content) + '...';
|
||||
}
|
||||
};
|
||||
|
||||
return _t(tree);
|
||||
}
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
module.exports = function(css) {
|
||||
var TokenType = require('../token-types');
|
||||
|
||||
var tokens = [],
|
||||
urlMode = false,
|
||||
blockMode = 0,
|
||||
c, // current character
|
||||
cn, // next character
|
||||
pos = 0,
|
||||
tn = 0,
|
||||
ln = 1,
|
||||
col = 1;
|
||||
|
||||
var Punctuation = {
|
||||
' ': TokenType.Space,
|
||||
'\n': TokenType.Newline,
|
||||
'\r': TokenType.Newline,
|
||||
'\t': TokenType.Tab,
|
||||
'!': TokenType.ExclamationMark,
|
||||
'"': TokenType.QuotationMark,
|
||||
'#': TokenType.NumberSign,
|
||||
'$': TokenType.DollarSign,
|
||||
'%': TokenType.PercentSign,
|
||||
'&': TokenType.Ampersand,
|
||||
'\'': TokenType.Apostrophe,
|
||||
'(': TokenType.LeftParenthesis,
|
||||
')': TokenType.RightParenthesis,
|
||||
'*': TokenType.Asterisk,
|
||||
'+': TokenType.PlusSign,
|
||||
',': TokenType.Comma,
|
||||
'-': TokenType.HyphenMinus,
|
||||
'.': TokenType.FullStop,
|
||||
'/': TokenType.Solidus,
|
||||
':': TokenType.Colon,
|
||||
';': TokenType.Semicolon,
|
||||
'<': TokenType.LessThanSign,
|
||||
'=': TokenType.EqualsSign,
|
||||
'>': TokenType.GreaterThanSign,
|
||||
'?': TokenType.QuestionMark,
|
||||
'@': TokenType.CommercialAt,
|
||||
'[': TokenType.LeftSquareBracket,
|
||||
']': TokenType.RightSquareBracket,
|
||||
'^': TokenType.CircumflexAccent,
|
||||
'_': TokenType.LowLine,
|
||||
'{': TokenType.LeftCurlyBracket,
|
||||
'|': TokenType.VerticalLine,
|
||||
'}': TokenType.RightCurlyBracket,
|
||||
'~': TokenType.Tilde
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a token to the token list
|
||||
* @param {string} type
|
||||
* @param {string} value
|
||||
*/
|
||||
function pushToken(type, value, column) {
|
||||
tokens.push({
|
||||
tn: tn++,
|
||||
ln: ln,
|
||||
col: column,
|
||||
type: type,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character is a decimal digit
|
||||
* @param {string} c Character
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isDecimalDigit(c) {
|
||||
return '0123456789'.indexOf(c) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse spaces
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseSpaces(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a non-space character:
|
||||
for (; pos < css.length; pos++) {
|
||||
if (css.charAt(pos) !== ' ') break;
|
||||
}
|
||||
|
||||
// Add a substring containing only spaces to tokens:
|
||||
pushToken(TokenType.Space, css.substring(start, pos--), col);
|
||||
col += (pos - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string within quotes
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
* @param {string} q Quote (either `'` or `"`)
|
||||
*/
|
||||
function parseString(css, q) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a matching quote:
|
||||
for (pos++; pos < css.length; pos++) {
|
||||
// Skip escaped quotes:
|
||||
if (css.charAt(pos) === '\\') pos++;
|
||||
else if (css.charAt(pos) === q) break;
|
||||
}
|
||||
|
||||
// Add the string (including quotes) to tokens:
|
||||
pushToken(q === '"' ? TokenType.StringDQ : TokenType.StringSQ, css.substring(start, pos + 1), col);
|
||||
col += (pos - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse numbers
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseDecimalNumber(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a character that's not a digit:
|
||||
for (; pos < css.length; pos++) {
|
||||
if (!isDecimalDigit(css.charAt(pos))) break;
|
||||
}
|
||||
|
||||
// Add the number to tokens:
|
||||
pushToken(TokenType.DecimalNumber, css.substring(start, pos--), col);
|
||||
col += (pos - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse identifier
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseIdentifier(css) {
|
||||
var start = pos;
|
||||
|
||||
// Skip all opening slashes:
|
||||
while (css.charAt(pos) === '/') pos++;
|
||||
|
||||
// Read the string until we meet a punctuation mark:
|
||||
for (; pos < css.length; pos++) {
|
||||
// Skip all '\':
|
||||
if (css.charAt(pos) === '\\') pos++;
|
||||
else if (css.charAt(pos) in Punctuation) break;
|
||||
}
|
||||
|
||||
var ident = css.substring(start, pos--);
|
||||
|
||||
// Enter url mode if parsed substring is `url`:
|
||||
urlMode = urlMode || ident === 'url';
|
||||
|
||||
// Add identifier to tokens:
|
||||
pushToken(TokenType.Identifier, ident, col);
|
||||
col += (pos - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a multiline comment
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseMLComment(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet `*/`.
|
||||
// Since we already know first 2 characters (`/*`), start reading
|
||||
// from `pos + 2`:
|
||||
for (pos = pos + 2; pos < css.length; pos++) {
|
||||
if (css.charAt(pos) === '*' && css.charAt(pos + 1) === '/') {
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add full comment (including `/*` and `*/`) to the list of tokens:
|
||||
var comment = css.substring(start, pos + 1);
|
||||
pushToken(TokenType.CommentML, comment, col);
|
||||
|
||||
var newlines = comment.split('\n');
|
||||
if (newlines.length > 1) {
|
||||
ln += newlines.length - 1;
|
||||
col = newlines[newlines.length - 1].length;
|
||||
} else {
|
||||
col += (pos - start);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single line comment
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseSLComment(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet line break.
|
||||
// Since we already know first 2 characters (`//`), start reading
|
||||
// from `pos + 2`:
|
||||
for (pos+=2; pos < css.length; pos++) {
|
||||
if (css.charAt(pos) === '\n' || css.charAt(pos) === '\r') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add comment (including `//` and line break) to the list of tokens:
|
||||
pushToken(TokenType.CommentSL, css.substring(start, pos--), col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a CSS string to a list of tokens
|
||||
* @param {string} css CSS string
|
||||
* @returns {Array} List of tokens
|
||||
* @private
|
||||
*/
|
||||
function getTokens(css) {
|
||||
// Parse string, character by character:
|
||||
for (pos = 0; pos < css.length; col++, pos++) {
|
||||
c = css.charAt(pos);
|
||||
cn = css.charAt(pos + 1);
|
||||
|
||||
// If we meet `/*`, it's a start of a multiline comment.
|
||||
// Parse following characters as a multiline comment:
|
||||
if (c === '/' && cn === '*') {
|
||||
parseMLComment(css);
|
||||
}
|
||||
|
||||
// If we meet `//` and it is not a part of url:
|
||||
else if (!urlMode && c === '/' && cn === '/') {
|
||||
// If we're currently inside a block, treat `//` as a start
|
||||
// of identifier. Else treat `//` as a start of a single-line
|
||||
// comment:
|
||||
parseSLComment(css);
|
||||
}
|
||||
|
||||
// If current character is a double or single quote, it's a start
|
||||
// of a string:
|
||||
else if (c === '"' || c === "'") {
|
||||
parseString(css, c);
|
||||
}
|
||||
|
||||
// If current character is a space:
|
||||
else if (c === ' ') {
|
||||
parseSpaces(css);
|
||||
}
|
||||
|
||||
// If current character is a punctuation mark:
|
||||
else if (c in Punctuation) {
|
||||
// Add it to the list of tokens:
|
||||
pushToken(Punctuation[c], c, col);
|
||||
if (c === '\n' || c === '\r') {
|
||||
ln++;
|
||||
col = 0;
|
||||
} // Go to next line
|
||||
if (c === ')') urlMode = false; // exit url mode
|
||||
if (c === '{') blockMode++; // enter a block
|
||||
if (c === '}') blockMode--; // exit a block
|
||||
}
|
||||
|
||||
// If current character is a decimal digit:
|
||||
else if (isDecimalDigit(c)) {
|
||||
parseDecimalNumber(css);
|
||||
}
|
||||
|
||||
// If current character is anything else:
|
||||
else {
|
||||
parseIdentifier(css);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
return getTokens(css);
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
module.exports = {
|
||||
ArgumentsType: 'arguments',
|
||||
AtkeywordType: 'atkeyword',
|
||||
AtrulebType: 'atruleb',
|
||||
AtrulerType: 'atruler',
|
||||
AtrulerqType: 'atrulerq',
|
||||
AtrulersType: 'atrulers',
|
||||
AtrulesType: 'atrules',
|
||||
AttribType: 'attribute',
|
||||
AttrselectorType: 'attributeSelector',
|
||||
BlockType: 'block',
|
||||
BracesType: 'braces',
|
||||
ClassType: 'class',
|
||||
CombinatorType: 'combinator',
|
||||
CommentMLType: 'multilineComment',
|
||||
CommentSLType: 'singlelineComment',
|
||||
ConditionType: 'condition',
|
||||
ConditionalStatementType: 'conditionalStatement',
|
||||
DeclarationType: 'declaration',
|
||||
DeclDelimType: 'declarationDelimiter',
|
||||
DefaultType: 'default',
|
||||
DelimType: 'delimiter',
|
||||
DimensionType: 'dimension',
|
||||
EscapedStringType: 'escapedString',
|
||||
FilterType: 'filter',
|
||||
FiltervType: 'filterValue',
|
||||
FunctionType: 'function',
|
||||
FunctionExpressionType: 'functionExpression',
|
||||
IdentType: 'ident',
|
||||
ImportantType: 'important',
|
||||
IncludeType: 'include',
|
||||
InterpolationType: 'interpolation',
|
||||
InterpolatedVariableType: 'interpolatedVariable',
|
||||
LoopType: 'loop',
|
||||
MixinType: 'mixin',
|
||||
NamespaceType: 'namespace',
|
||||
NthType: 'nth',
|
||||
NthselectorType: 'nthSelector',
|
||||
NumberType: 'number',
|
||||
OperatorType: 'operator',
|
||||
ParentSelectorType: 'parentSelector',
|
||||
PercentageType: 'percentage',
|
||||
PlaceholderType: 'placeholder',
|
||||
ProgidType: 'progid',
|
||||
PropertyType: 'property',
|
||||
PropertyDelimType: 'propertyDelimiter',
|
||||
PseudocType: 'pseudoClass',
|
||||
PseudoeType: 'pseudoElement',
|
||||
RawType: 'raw',
|
||||
RulesetType: 'ruleset',
|
||||
SType: 'space',
|
||||
SelectorType: 'selector',
|
||||
ShashType: 'id',
|
||||
SimpleselectorType: 'simpleSelector',
|
||||
StringType: 'string',
|
||||
StylesheetType: 'stylesheet',
|
||||
UnaryType: 'unaryOperator',
|
||||
UriType: 'uri',
|
||||
ValueType: 'value',
|
||||
VariableType: 'variable',
|
||||
VariablesListType: 'variablesList',
|
||||
VhashType: 'color'
|
||||
};
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {array|string} content
|
||||
* @param {number} line
|
||||
* @param {number} column
|
||||
* @constructor
|
||||
*/
|
||||
function Node(type, content, line, column) {
|
||||
this.type = type;
|
||||
this.content = content;
|
||||
this.start = {
|
||||
line: line,
|
||||
column: column
|
||||
};
|
||||
}
|
||||
|
||||
Node.prototype = {
|
||||
type: null,
|
||||
|
||||
content: null,
|
||||
|
||||
start: null,
|
||||
|
||||
/**
|
||||
* @param {String} type Node type
|
||||
* @return {Boolean} Whether there is a child node of given type
|
||||
*/
|
||||
contains: function(type) {
|
||||
return this.content.some(function(node) {
|
||||
return node.type === type;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} type
|
||||
* @return {Node} First child node
|
||||
*/
|
||||
first: function(type) {
|
||||
if (!type || !Array.isArray(this.content)) return this.content[0];
|
||||
|
||||
var i = 0;
|
||||
var l = this.content.length;
|
||||
|
||||
for (; i < l; i++) {
|
||||
if (this.content[i].type === type) return this.content[i];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} type Node type
|
||||
* @param {Function} callback Function to call for every found node
|
||||
*/
|
||||
forEach: function(type, callback) {
|
||||
if (!Array.isArray(this.content)) return;
|
||||
|
||||
if (typeof type !== 'string') callback = type, type = null;
|
||||
|
||||
var i = 0;
|
||||
var l = this.content.length;
|
||||
|
||||
for (; i < l; i++) {
|
||||
if (!type || this.content[i] && this.content[i].type === type)
|
||||
callback(this.content[i], i);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Number} index
|
||||
* @return {Node}
|
||||
*/
|
||||
get: function(index) {
|
||||
return Array.isArray(this.content) && this.content[index];
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Number} index
|
||||
* @param {Node} node
|
||||
*/
|
||||
insert: function(index, node) {
|
||||
Array.isArray(this.content) && this.content.splice(index, 0, node);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} type
|
||||
* @return {Boolean} Whether the node is of given type
|
||||
*/
|
||||
is: function(type) {
|
||||
return this.type === type;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} type
|
||||
* @return {Node} Last child node
|
||||
*/
|
||||
last: function(type) {
|
||||
var i = this.content.length - 1;
|
||||
|
||||
if (!type || !Array.isArray(this.content))
|
||||
return this.content[i];
|
||||
|
||||
|
||||
for (;;i--) {
|
||||
if (this.content[i].type === type) return this.content[i];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Function} callback
|
||||
*/
|
||||
map: function(callback) {
|
||||
callback(this);
|
||||
|
||||
if (!Array.isArray(this.content)) return;
|
||||
|
||||
this.content.forEach(function(node) {
|
||||
if (node instanceof Node)
|
||||
node.map(callback);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Number} index
|
||||
*/
|
||||
remove: function(index) {
|
||||
Array.isArray(this.content) && this.content.splice(index, 1);
|
||||
},
|
||||
|
||||
get length() {
|
||||
return this.content.length;
|
||||
},
|
||||
|
||||
toString: function() {
|
||||
return JSON.stringify(this, false, 2);
|
||||
},
|
||||
|
||||
//TODO(tonyganch): Save syntax name while creating a node.
|
||||
toCSS: function(syntax) {
|
||||
if (!syntax) return console.error('Empty syntax name.');
|
||||
|
||||
try {
|
||||
stringify = require('./' + syntax + '/stringify');
|
||||
} catch (e) {
|
||||
var message = 'Syntax "' + syntax + '" is not supported yet, sorry';
|
||||
return console.error(message);
|
||||
}
|
||||
|
||||
return stringify(this);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Node;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
var fs = require('fs');
|
||||
var ParsingError = require('./parsing-error');
|
||||
|
||||
var Defaults = {
|
||||
SYNTAX: 'css',
|
||||
NEED_INFO: false,
|
||||
CSS_RULE: 'stylesheet',
|
||||
JS_RULE: 'program'
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} css
|
||||
* @param {Object} options
|
||||
* @return {Object} AST
|
||||
*/
|
||||
function parse(css, options) {
|
||||
if (!css || typeof css !== 'string')
|
||||
throw new Error('Please, pass a string to parse');
|
||||
|
||||
var syntax = options && options.syntax || Defaults.SYNTAX;
|
||||
var needInfo = options && options.needInfo || Defaults.NEED_INFO;
|
||||
var rule = options && options.rule ||
|
||||
(syntax === 'js' ? Defaults.JS_RULE : Defaults.CSS_RULE);
|
||||
|
||||
if (!fs.existsSync(__dirname + '/' + syntax))
|
||||
return console.error('Syntax "' + syntax + '" is not supported yet, sorry');
|
||||
|
||||
var getTokens = require('./' + syntax + '/tokenizer');
|
||||
var mark = require('./' + syntax + '/mark');
|
||||
var parse = require('./' + syntax + '/parse');
|
||||
|
||||
var tokens = getTokens(css);
|
||||
mark(tokens);
|
||||
|
||||
try {
|
||||
return parse(tokens, rule, needInfo);
|
||||
} catch (e) {
|
||||
throw new ParsingError(e, css);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = parse;
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
var parserPackage = require('../package.json');
|
||||
|
||||
/**
|
||||
* @param {Error} e
|
||||
* @param {String} css
|
||||
*/
|
||||
function ParsingError(e, css) {
|
||||
this.line = e.line;
|
||||
this.syntax = e.syntax;
|
||||
this.css_ = css;
|
||||
}
|
||||
|
||||
ParsingError.prototype = {
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
line: null,
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
*/
|
||||
name: 'Parsing error',
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
*/
|
||||
syntax: null,
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
*/
|
||||
version: parserPackage.version,
|
||||
|
||||
/**
|
||||
* @return {String}
|
||||
*/
|
||||
toString: function() {
|
||||
return this.name + ': ' + this.message;
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
*/
|
||||
get message() {
|
||||
return [
|
||||
'Please check the validity of the block starting from line #' + this.line,
|
||||
'',
|
||||
this.codeFragment_,
|
||||
'',
|
||||
'Gonzales PE version: ' + this.version,
|
||||
'Syntax: ' + this.syntax
|
||||
].join('\n');
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
*/
|
||||
get codeFragment_() {
|
||||
var LINES_AROUND = 2;
|
||||
|
||||
var result = [];
|
||||
var currentLineNumber = this.line;
|
||||
var start = currentLineNumber - 1 - LINES_AROUND;
|
||||
var end = currentLineNumber + LINES_AROUND;
|
||||
var lines = this.css_.split(/\r\n|\r|\n/);
|
||||
|
||||
for (var i = start; i < end; i++) {
|
||||
var line = lines[i];
|
||||
if (!line) continue;
|
||||
var ln = i + 1;
|
||||
var mark = ln === currentLineNumber ? '*' : ' ';
|
||||
result.push(ln + mark + '| ' + line);
|
||||
}
|
||||
|
||||
return result.join('\n');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ParsingError;
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
var TokenType = require('../token-types');
|
||||
|
||||
module.exports = (function() {
|
||||
/**
|
||||
* Mark whitespaces and comments
|
||||
*/
|
||||
function markSC(tokens) {
|
||||
var tokensLength = tokens.length,
|
||||
ws = -1, // flag for whitespaces
|
||||
sc = -1, // flag for whitespaces and comments
|
||||
t; // current token
|
||||
|
||||
// For every token in the token list, mark spaces and line breaks
|
||||
// as spaces (set both `ws` and `sc` flags). Mark multiline comments
|
||||
// with `sc` flag.
|
||||
// If there are several spaces or tabs or line breaks or multiline
|
||||
// comments in a row, group them: take the last one's index number
|
||||
// and save it to the first token in the group as a reference
|
||||
// (e.g., `ws_last = 7` for a group of whitespaces or `sc_last = 9`
|
||||
// for a group of whitespaces and comments):
|
||||
for (var i = 0; i < tokensLength; i++) {
|
||||
t = tokens[i];
|
||||
switch (t.type) {
|
||||
case TokenType.Space:
|
||||
case TokenType.Tab:
|
||||
t.ws = true;
|
||||
t.sc = true;
|
||||
|
||||
if (ws === -1) ws = i;
|
||||
if (sc === -1) sc = i;
|
||||
|
||||
break;
|
||||
case TokenType.Newline:
|
||||
t.ws = true;
|
||||
t.sc = true;
|
||||
|
||||
ws = ws === -1 ? i : ws;
|
||||
sc = sc === -1 ? i : ws;
|
||||
|
||||
tokens[ws].ws_last = i - 1;
|
||||
tokens[sc].sc_last = i - 1;
|
||||
tokens[i].ws_last = i;
|
||||
tokens[i].sc_last = i;
|
||||
|
||||
ws = -1;
|
||||
sc = -1;
|
||||
|
||||
break;
|
||||
case TokenType.CommentML:
|
||||
case TokenType.CommentSL:
|
||||
if (ws !== -1) {
|
||||
tokens[ws].ws_last = i - 1;
|
||||
ws = -1;
|
||||
}
|
||||
|
||||
t.sc = true;
|
||||
|
||||
break;
|
||||
default:
|
||||
if (ws !== -1) {
|
||||
tokens[ws].ws_last = i - 1;
|
||||
ws = -1;
|
||||
}
|
||||
|
||||
if (sc !== -1) {
|
||||
tokens[sc].sc_last = i - 1;
|
||||
sc = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ws !== -1) tokens[ws].ws_last = i - 1;
|
||||
if (sc !== -1) tokens[sc].sc_last = i - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pair brackets
|
||||
*/
|
||||
function markBrackets(tokens) {
|
||||
var tokensLength = tokens.length;
|
||||
var ps = [], // parenthesis
|
||||
sbs = [], // square brackets
|
||||
cbs = [], // curly brackets
|
||||
t; // current token
|
||||
|
||||
// For every token in the token list, if we meet an opening (left)
|
||||
// bracket, push its index number to a corresponding array.
|
||||
// If we then meet a closing (right) bracket, look at the corresponding
|
||||
// array. If there are any elements (records about previously met
|
||||
// left brackets), take a token of the last left bracket (take
|
||||
// the last index number from the array and find a token with
|
||||
// this index number) and save right bracket's index as a reference:
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
t = tokens[i];
|
||||
switch(t.type) {
|
||||
case TokenType.LeftParenthesis:
|
||||
ps.push(i);
|
||||
break;
|
||||
case TokenType.RightParenthesis:
|
||||
if (ps.length) {
|
||||
t.left = ps.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
break;
|
||||
case TokenType.LeftSquareBracket:
|
||||
sbs.push(i);
|
||||
break;
|
||||
case TokenType.RightSquareBracket:
|
||||
if (sbs.length) {
|
||||
t.left = sbs.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
break;
|
||||
case TokenType.LeftCurlyBracket:
|
||||
cbs.push(i);
|
||||
break;
|
||||
case TokenType.RightCurlyBracket:
|
||||
if (cbs.length) {
|
||||
t.left = cbs.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function markBlocks(tokens) {
|
||||
var tokensLength = tokens.length;
|
||||
var blocks = [],
|
||||
currentLN = 1,
|
||||
currentIL = 0,
|
||||
prevIL = 0,
|
||||
i = 0,
|
||||
l = tokens.length,
|
||||
iw;
|
||||
|
||||
for (; i != l; i++) {
|
||||
if (!tokens[i - 1]) continue;
|
||||
|
||||
// Skip all tokens on current line:
|
||||
if (tokens[i].ln == currentLN) continue;
|
||||
else currentLN = tokens[i].ln;
|
||||
|
||||
// Get indent level:
|
||||
prevIL = currentIL;
|
||||
if (tokens[i].type === TokenType.Newline) continue;
|
||||
else if (tokens[i].type === TokenType.Space &&
|
||||
tokens[i + 1] &&
|
||||
tokens[i + 1].type === TokenType.Newline) continue;
|
||||
else if (tokens[i].type !== TokenType.Space) currentIL = 0;
|
||||
else {
|
||||
// If we don't know ident width yet, count number of spaces:
|
||||
if (!iw) iw = tokens[i].value.length;
|
||||
prevIL = currentIL;
|
||||
currentIL = tokens[i].value.length / iw;
|
||||
}
|
||||
|
||||
// Decide whether it's block's start or end:
|
||||
if (prevIL === currentIL) continue;
|
||||
else if (currentIL > prevIL) {
|
||||
blocks.push(i);
|
||||
continue;
|
||||
} else {
|
||||
var il = prevIL;
|
||||
while (blocks.length > 0 && il !== currentIL) {
|
||||
tokens[blocks.pop()].block_end = i - 1;
|
||||
il--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (blocks.length > 0) {
|
||||
tokens[blocks.pop()].block_end = i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return function(tokens) {
|
||||
markBrackets(tokens);
|
||||
markSC(tokens);
|
||||
markBlocks(tokens);
|
||||
}
|
||||
})();
|
||||
+3889
File diff suppressed because it is too large
Load Diff
+98
@@ -0,0 +1,98 @@
|
||||
module.exports = function stringify(tree) {
|
||||
// TODO: Better error message
|
||||
if (!tree) throw new Error('We need tree to translate');
|
||||
|
||||
function _t(tree) {
|
||||
var type = tree.type;
|
||||
if (_unique[type]) return _unique[type](tree);
|
||||
if (typeof tree.content === 'string') return tree.content;
|
||||
if (Array.isArray(tree.content)) return _composite(tree.content);
|
||||
return '';
|
||||
}
|
||||
|
||||
function _composite(t, i) {
|
||||
if (!t) return '';
|
||||
|
||||
var s = '';
|
||||
i = i || 0;
|
||||
for (; i < t.length; i++) s += _t(t[i]);
|
||||
return s;
|
||||
}
|
||||
|
||||
var _unique = {
|
||||
'arguments': function(t) {
|
||||
return '(' + _composite(t.content) + ')';
|
||||
},
|
||||
'atkeyword': function(t) {
|
||||
return '@' + _composite(t.content);
|
||||
},
|
||||
'atruler': function(t) {
|
||||
return _t(t.content[0]) + _t(t.content[1]) + '{' + _t(t.content[2]) + '}';
|
||||
},
|
||||
'attribute': function(t) {
|
||||
return '[' + _composite(t.content) + ']';
|
||||
},
|
||||
'block': function(t) {
|
||||
return _composite(t.content);
|
||||
},
|
||||
'braces': function(t) {
|
||||
return t.content[0] + _composite(t.content.slice(2)) + t.content[1];
|
||||
},
|
||||
'class': function(t) {
|
||||
return '.' + _composite(t.content);
|
||||
},
|
||||
'color': function(t) {
|
||||
return '#' + t.content;
|
||||
},
|
||||
'default': function(t) {
|
||||
return '!' + _composite(t.content) + 'default';
|
||||
},
|
||||
'filter': function(t) {
|
||||
return _t(t.content[0]) + ':' + _t(t.content[1]);
|
||||
},
|
||||
'functionExpression': function(t) {
|
||||
return 'expression(' + t.content + ')';
|
||||
},
|
||||
'id': function (t) {
|
||||
return '#' + t.content;
|
||||
},
|
||||
'important': function(t) {
|
||||
return '!' + _composite(t.content) + 'important';
|
||||
},
|
||||
'interpolation': function(t) {
|
||||
return '#{' + _composite(t.content) + '}';
|
||||
},
|
||||
'multilineComment': function (t) {
|
||||
return '/*' + t.content;
|
||||
},
|
||||
'nthSelector': function(t) {
|
||||
return ':' + _t(t.content[0]) + '(' + _composite(t.content.slice(1)) + ')';
|
||||
},
|
||||
'percentage': function(t) {
|
||||
return _composite(t.content) + '%';
|
||||
},
|
||||
'placeholder': function(t) {
|
||||
return '%' + _composite(t.content);
|
||||
},
|
||||
'pseudoClass': function(t) {
|
||||
return ':' + _composite(t.content);
|
||||
},
|
||||
'pseudoElement': function(t) {
|
||||
return '::' + _composite(t.content);
|
||||
},
|
||||
'singlelineComment': function (t) {
|
||||
return '/' + '/' + t.content;
|
||||
},
|
||||
'uri': function(t) {
|
||||
return 'url(' + _composite(t.content) + ')';
|
||||
},
|
||||
'variable': function(t) {
|
||||
return '$' + _composite(t.content);
|
||||
},
|
||||
'variablesList': function(t) {
|
||||
return _composite(t.content) + '...';
|
||||
}
|
||||
};
|
||||
|
||||
return _t(tree);
|
||||
}
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
module.exports = function(css) {
|
||||
var TokenType = require('../token-types');
|
||||
|
||||
var tokens = [],
|
||||
urlMode = false,
|
||||
blockMode = 0,
|
||||
c, // current character
|
||||
cn, // next character
|
||||
pos = 0,
|
||||
tn = 0,
|
||||
ln = 1,
|
||||
col = 1;
|
||||
|
||||
var Punctuation = {
|
||||
' ': TokenType.Space,
|
||||
'\n': TokenType.Newline,
|
||||
'\r': TokenType.Newline,
|
||||
'\t': TokenType.Tab,
|
||||
'!': TokenType.ExclamationMark,
|
||||
'"': TokenType.QuotationMark,
|
||||
'#': TokenType.NumberSign,
|
||||
'$': TokenType.DollarSign,
|
||||
'%': TokenType.PercentSign,
|
||||
'&': TokenType.Ampersand,
|
||||
'\'': TokenType.Apostrophe,
|
||||
'(': TokenType.LeftParenthesis,
|
||||
')': TokenType.RightParenthesis,
|
||||
'*': TokenType.Asterisk,
|
||||
'+': TokenType.PlusSign,
|
||||
',': TokenType.Comma,
|
||||
'-': TokenType.HyphenMinus,
|
||||
'.': TokenType.FullStop,
|
||||
'/': TokenType.Solidus,
|
||||
':': TokenType.Colon,
|
||||
';': TokenType.Semicolon,
|
||||
'<': TokenType.LessThanSign,
|
||||
'=': TokenType.EqualsSign,
|
||||
'==': TokenType.EqualitySign,
|
||||
'!=': TokenType.InequalitySign,
|
||||
'>': TokenType.GreaterThanSign,
|
||||
'?': TokenType.QuestionMark,
|
||||
'@': TokenType.CommercialAt,
|
||||
'[': TokenType.LeftSquareBracket,
|
||||
']': TokenType.RightSquareBracket,
|
||||
'^': TokenType.CircumflexAccent,
|
||||
'_': TokenType.LowLine,
|
||||
'{': TokenType.LeftCurlyBracket,
|
||||
'|': TokenType.VerticalLine,
|
||||
'}': TokenType.RightCurlyBracket,
|
||||
'~': TokenType.Tilde
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a token to the token list
|
||||
* @param {string} type
|
||||
* @param {string} value
|
||||
*/
|
||||
function pushToken(type, value, column) {
|
||||
tokens.push({
|
||||
tn: tn++,
|
||||
ln: ln,
|
||||
col: column,
|
||||
type: type,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character is a decimal digit
|
||||
* @param {string} c Character
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isDecimalDigit(c) {
|
||||
return '0123456789'.indexOf(c) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse spaces
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseSpaces(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a non-space character:
|
||||
for (; pos < css.length; pos++) {
|
||||
if (css.charAt(pos) !== ' ') break;
|
||||
}
|
||||
|
||||
// Add a substring containing only spaces to tokens:
|
||||
pushToken(TokenType.Space, css.substring(start, pos--), col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string within quotes
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
* @param {string} q Quote (either `'` or `"`)
|
||||
*/
|
||||
function parseString(css, q) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a matching quote:
|
||||
for (pos++; pos < css.length; pos++) {
|
||||
// Skip escaped quotes:
|
||||
if (css.charAt(pos) === '\\') pos++;
|
||||
else if (css.charAt(pos) === q) break;
|
||||
}
|
||||
|
||||
// Add the string (including quotes) to tokens:
|
||||
pushToken(q === '"' ? TokenType.StringDQ : TokenType.StringSQ, css.substring(start, pos + 1), col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse numbers
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseDecimalNumber(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a character that's not a digit:
|
||||
for (; pos < css.length; pos++) {
|
||||
if (!isDecimalDigit(css.charAt(pos))) break;
|
||||
}
|
||||
|
||||
// Add the number to tokens:
|
||||
pushToken(TokenType.DecimalNumber, css.substring(start, pos--), col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse identifier
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseIdentifier(css) {
|
||||
var start = pos;
|
||||
|
||||
// Skip all opening slashes:
|
||||
while (css.charAt(pos) === '/') pos++;
|
||||
|
||||
// Read the string until we meet a punctuation mark:
|
||||
for (; pos < css.length; pos++) {
|
||||
// Skip all '\':
|
||||
if (css.charAt(pos) === '\\') pos++;
|
||||
else if (css.charAt(pos) in Punctuation) break;
|
||||
}
|
||||
|
||||
var ident = css.substring(start, pos--);
|
||||
|
||||
// Enter url mode if parsed substring is `url`:
|
||||
urlMode = urlMode || ident === 'url';
|
||||
|
||||
// Add identifier to tokens:
|
||||
pushToken(TokenType.Identifier, ident, col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse equality sign
|
||||
* @param {string} sass Unparsed part of SASS string
|
||||
*/
|
||||
function parseEquality(css) {
|
||||
pushToken(TokenType.EqualitySign, '==', col);
|
||||
pos++;
|
||||
col++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse inequality sign
|
||||
* @param {string} sass Unparsed part of SASS string
|
||||
*/
|
||||
function parseInequality(css) {
|
||||
pushToken(TokenType.InequalitySign, '!=', col);
|
||||
pos++;
|
||||
col++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a multiline comment
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseMLComment(css) {
|
||||
var start = pos;
|
||||
|
||||
// Get current indent level:
|
||||
var il = 0;
|
||||
for (var _pos = pos - 1; _pos > -1; _pos--) {
|
||||
// TODO: Can be tabs:
|
||||
if (css.charAt(_pos) === ' ') il++;
|
||||
else break;
|
||||
}
|
||||
|
||||
for (pos += 2; pos < css.length; pos++) {
|
||||
if (css.charAt(pos) === '\n') {
|
||||
// Get new line's indent level:
|
||||
var _il = 0;
|
||||
for (var _pos = pos + 1; _pos < css.length; _pos++) {
|
||||
if (css.charAt(_pos) === ' ') _il++;
|
||||
else break;
|
||||
}
|
||||
|
||||
if (_il > il) {
|
||||
col = 0;
|
||||
pos += _pos - pos;
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add full comment (including `/*`) to the list of tokens:
|
||||
var comment = css.substring(start, pos + 1);
|
||||
pushToken(TokenType.CommentML, comment, col);
|
||||
|
||||
var newlines = comment.split('\n');
|
||||
if (newlines.length > 1) {
|
||||
ln += newlines.length - 1;
|
||||
col = newlines[newlines.length - 1].length;
|
||||
} else {
|
||||
col += (pos - start);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single line comment
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseSLComment(css) {
|
||||
var start = pos;
|
||||
|
||||
// Check if comment is the only token on the line, and if so,
|
||||
// get current indent level:
|
||||
var il = 0;
|
||||
var onlyToken = false;
|
||||
for (var _pos = pos - 1; _pos > -1; _pos--) {
|
||||
// TODO: Can be tabs:
|
||||
if (css.charAt(_pos) === ' ') il++;
|
||||
else if (css.charAt(_pos) === '\n') {
|
||||
onlyToken = true;
|
||||
break;
|
||||
} else break;
|
||||
}
|
||||
if (_pos === -1) onlyToken = true;
|
||||
|
||||
// Read the string until we meet comment end.
|
||||
// Since we already know first 2 characters (`//`), start reading
|
||||
// from `pos + 2`:
|
||||
if (!onlyToken) {
|
||||
for (pos += 2; pos < css.length; pos++) {
|
||||
if (css.charAt(pos) === '\n' || css.charAt(pos) === '\r') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (pos+=2; pos < css.length; pos++) {
|
||||
if (css.charAt(pos) === '\n') {
|
||||
// Get new line's indent level:
|
||||
var _il = 0;
|
||||
for (var _pos = pos + 1; _pos < css.length; _pos++) {
|
||||
if (css.charAt(_pos) === ' ') _il++;
|
||||
else break;
|
||||
}
|
||||
|
||||
if (_il > il) {
|
||||
col = 0;
|
||||
pos += _pos - pos;
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add comment (including `//` and line break) to the list of tokens:
|
||||
pushToken(TokenType.CommentSL, css.substring(start, pos--), col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a CSS string to a list of tokens
|
||||
* @param {string} css CSS string
|
||||
* @returns {Array} List of tokens
|
||||
* @private
|
||||
*/
|
||||
function getTokens(css) {
|
||||
// Parse string, character by character:
|
||||
for (pos = 0; pos < css.length; col++, pos++) {
|
||||
c = css.charAt(pos);
|
||||
cn = css.charAt(pos + 1);
|
||||
|
||||
// If we meet `/*`, it's a start of a multiline comment.
|
||||
// Parse following characters as a multiline comment:
|
||||
if (c === '/' && cn === '*') {
|
||||
parseMLComment(css);
|
||||
}
|
||||
|
||||
// If we meet `//` and it is not a part of url:
|
||||
else if (!urlMode && c === '/' && cn === '/') {
|
||||
// If we're currently inside a block, treat `//` as a start
|
||||
// of identifier. Else treat `//` as a start of a single-line
|
||||
// comment:
|
||||
parseSLComment(css);
|
||||
}
|
||||
|
||||
// If current character is a double or single quote, it's a start
|
||||
// of a string:
|
||||
else if (c === '"' || c === "'") {
|
||||
parseString(css, c);
|
||||
}
|
||||
|
||||
// If current character is a space:
|
||||
else if (c === ' ') {
|
||||
parseSpaces(css);
|
||||
}
|
||||
|
||||
// If current character is `=`, it must be combined with next `=`
|
||||
else if (c === '=' && cn === '=') {
|
||||
parseEquality(css);
|
||||
}
|
||||
|
||||
// If we meet `!=`, this must be inequality
|
||||
else if (c === '!' && cn === '=') {
|
||||
parseInequality(css);
|
||||
}
|
||||
|
||||
// If current character is a punctuation mark:
|
||||
else if (c in Punctuation) {
|
||||
// Add it to the list of tokens:
|
||||
pushToken(Punctuation[c], c, col);
|
||||
if (c === '\n' || c === '\r') {
|
||||
ln++;
|
||||
col = 0;
|
||||
} // Go to next line
|
||||
if (c === ')') urlMode = false; // exit url mode
|
||||
if (c === '{') blockMode++; // enter a block
|
||||
if (c === '}') blockMode--; // exit a block
|
||||
}
|
||||
|
||||
// If current character is a decimal digit:
|
||||
else if (isDecimalDigit(c)) {
|
||||
parseDecimalNumber(css);
|
||||
}
|
||||
|
||||
// If current character is anything else:
|
||||
else {
|
||||
parseIdentifier(css);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
return getTokens(css);
|
||||
};
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
var TokenType = require('../token-types');
|
||||
|
||||
module.exports = (function() {
|
||||
/**
|
||||
* Mark whitespaces and comments
|
||||
*/
|
||||
function markSC(tokens) {
|
||||
var tokensLength = tokens.length,
|
||||
ws = -1, // flag for whitespaces
|
||||
sc = -1, // flag for whitespaces and comments
|
||||
t; // current token
|
||||
|
||||
// For every token in the token list, mark spaces and line breaks
|
||||
// as spaces (set both `ws` and `sc` flags). Mark multiline comments
|
||||
// with `sc` flag.
|
||||
// If there are several spaces or tabs or line breaks or multiline
|
||||
// comments in a row, group them: take the last one's index number
|
||||
// and save it to the first token in the group as a reference
|
||||
// (e.g., `ws_last = 7` for a group of whitespaces or `sc_last = 9`
|
||||
// for a group of whitespaces and comments):
|
||||
for (var i = 0; i < tokensLength; i++) {
|
||||
t = tokens[i];
|
||||
switch (t.type) {
|
||||
case TokenType.Space:
|
||||
case TokenType.Tab:
|
||||
case TokenType.Newline:
|
||||
t.ws = true;
|
||||
t.sc = true;
|
||||
|
||||
if (ws === -1) ws = i;
|
||||
if (sc === -1) sc = i;
|
||||
|
||||
break;
|
||||
case TokenType.CommentML:
|
||||
case TokenType.CommentSL:
|
||||
if (ws !== -1) {
|
||||
tokens[ws].ws_last = i - 1;
|
||||
ws = -1;
|
||||
}
|
||||
|
||||
t.sc = true;
|
||||
|
||||
break;
|
||||
default:
|
||||
if (ws !== -1) {
|
||||
tokens[ws].ws_last = i - 1;
|
||||
ws = -1;
|
||||
}
|
||||
|
||||
if (sc !== -1) {
|
||||
tokens[sc].sc_last = i - 1;
|
||||
sc = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ws !== -1) tokens[ws].ws_last = i - 1;
|
||||
if (sc !== -1) tokens[sc].sc_last = i - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pair brackets
|
||||
*/
|
||||
function markBrackets(tokens) {
|
||||
var tokensLength = tokens.length;
|
||||
var ps = [], // parenthesis
|
||||
sbs = [], // square brackets
|
||||
cbs = [], // curly brackets
|
||||
t; // current token
|
||||
|
||||
// For every token in the token list, if we meet an opening (left)
|
||||
// bracket, push its index number to a corresponding array.
|
||||
// If we then meet a closing (right) bracket, look at the corresponding
|
||||
// array. If there are any elements (records about previously met
|
||||
// left brackets), take a token of the last left bracket (take
|
||||
// the last index number from the array and find a token with
|
||||
// this index number) and save right bracket's index as a reference:
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
t = tokens[i];
|
||||
switch(t.type) {
|
||||
case TokenType.LeftParenthesis:
|
||||
ps.push(i);
|
||||
break;
|
||||
case TokenType.RightParenthesis:
|
||||
if (ps.length) {
|
||||
t.left = ps.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
break;
|
||||
case TokenType.LeftSquareBracket:
|
||||
sbs.push(i);
|
||||
break;
|
||||
case TokenType.RightSquareBracket:
|
||||
if (sbs.length) {
|
||||
t.left = sbs.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
break;
|
||||
case TokenType.LeftCurlyBracket:
|
||||
cbs.push(i);
|
||||
break;
|
||||
case TokenType.RightCurlyBracket:
|
||||
if (cbs.length) {
|
||||
t.left = cbs.pop();
|
||||
tokens[t.left].right = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return function(tokens) {
|
||||
markBrackets(tokens);
|
||||
markSC(tokens);
|
||||
}
|
||||
})();
|
||||
+3552
File diff suppressed because it is too large
Load Diff
+98
@@ -0,0 +1,98 @@
|
||||
module.exports = function stringify(tree) {
|
||||
// TODO: Better error message
|
||||
if (!tree) throw new Error('We need tree to translate');
|
||||
|
||||
function _t(tree) {
|
||||
var type = tree.type;
|
||||
if (_unique[type]) return _unique[type](tree);
|
||||
if (typeof tree.content === 'string') return tree.content;
|
||||
if (Array.isArray(tree.content)) return _composite(tree.content);
|
||||
return '';
|
||||
}
|
||||
|
||||
function _composite(t, i) {
|
||||
if (!t) return '';
|
||||
|
||||
var s = '';
|
||||
i = i || 0;
|
||||
for (; i < t.length; i++) s += _t(t[i]);
|
||||
return s;
|
||||
}
|
||||
|
||||
var _unique = {
|
||||
'arguments': function(t) {
|
||||
return '(' + _composite(t.content) + ')';
|
||||
},
|
||||
'atkeyword': function(t) {
|
||||
return '@' + _composite(t.content);
|
||||
},
|
||||
'atruler': function(t) {
|
||||
return _t(t.content[0]) + _t(t.content[1]) + '{' + _t(t.content[2]) + '}';
|
||||
},
|
||||
'attribute': function(t) {
|
||||
return '[' + _composite(t.content) + ']';
|
||||
},
|
||||
'block': function(t) {
|
||||
return '{' + _composite(t.content) + '}';
|
||||
},
|
||||
'braces': function(t) {
|
||||
return t.content[0] + _composite(t.content.slice(2)) + t.content[1];
|
||||
},
|
||||
'class': function(t) {
|
||||
return '.' + _composite(t.content);
|
||||
},
|
||||
'color': function(t) {
|
||||
return '#' + t.content;
|
||||
},
|
||||
'default': function(t) {
|
||||
return '!' + _composite(t.content) + 'default';
|
||||
},
|
||||
'filter': function(t) {
|
||||
return _t(t.content[0]) + ':' + _t(t.content[1]);
|
||||
},
|
||||
'functionExpression': function(t) {
|
||||
return 'expression(' + t.content + ')';
|
||||
},
|
||||
'id': function (t) {
|
||||
return '#' + t.content;
|
||||
},
|
||||
'important': function(t) {
|
||||
return '!' + _composite(t.content) + 'important';
|
||||
},
|
||||
'interpolation': function(t) {
|
||||
return '#{' + _composite(t.content) + '}';
|
||||
},
|
||||
'multilineComment': function (t) {
|
||||
return '/*' + t.content + '*/';
|
||||
},
|
||||
'nthSelector': function(t) {
|
||||
return ':' + _t(t.content[0]) + '(' + _composite(t.content.slice(1)) + ')';
|
||||
},
|
||||
'percentage': function(t) {
|
||||
return _composite(t.content) + '%';
|
||||
},
|
||||
'placeholder': function(t) {
|
||||
return '%' + _composite(t.content);
|
||||
},
|
||||
'pseudoClass': function(t) {
|
||||
return ':' + _composite(t.content);
|
||||
},
|
||||
'pseudoElement': function(t) {
|
||||
return '::' + _composite(t.content);
|
||||
},
|
||||
'singlelineComment': function (t) {
|
||||
return '/' + '/' + t.content;
|
||||
},
|
||||
'uri': function(t) {
|
||||
return 'url(' + _composite(t.content) + ')';
|
||||
},
|
||||
'variable': function(t) {
|
||||
return '$' + _composite(t.content);
|
||||
},
|
||||
'variablesList': function(t) {
|
||||
return _composite(t.content) + '...';
|
||||
}
|
||||
};
|
||||
|
||||
return _t(tree);
|
||||
}
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
module.exports = function(css) {
|
||||
var TokenType = require('../token-types');
|
||||
|
||||
var tokens = [],
|
||||
urlMode = false,
|
||||
blockMode = 0,
|
||||
c, // current character
|
||||
cn, // next character
|
||||
pos = 0,
|
||||
tn = 0,
|
||||
ln = 1,
|
||||
col = 1;
|
||||
|
||||
var Punctuation = {
|
||||
' ': TokenType.Space,
|
||||
'\n': TokenType.Newline,
|
||||
'\r': TokenType.Newline,
|
||||
'\t': TokenType.Tab,
|
||||
'!': TokenType.ExclamationMark,
|
||||
'"': TokenType.QuotationMark,
|
||||
'#': TokenType.NumberSign,
|
||||
'$': TokenType.DollarSign,
|
||||
'%': TokenType.PercentSign,
|
||||
'&': TokenType.Ampersand,
|
||||
'\'': TokenType.Apostrophe,
|
||||
'(': TokenType.LeftParenthesis,
|
||||
')': TokenType.RightParenthesis,
|
||||
'*': TokenType.Asterisk,
|
||||
'+': TokenType.PlusSign,
|
||||
',': TokenType.Comma,
|
||||
'-': TokenType.HyphenMinus,
|
||||
'.': TokenType.FullStop,
|
||||
'/': TokenType.Solidus,
|
||||
':': TokenType.Colon,
|
||||
';': TokenType.Semicolon,
|
||||
'<': TokenType.LessThanSign,
|
||||
'=': TokenType.EqualsSign,
|
||||
'==': TokenType.EqualitySign,
|
||||
'!=': TokenType.InequalitySign,
|
||||
'>': TokenType.GreaterThanSign,
|
||||
'?': TokenType.QuestionMark,
|
||||
'@': TokenType.CommercialAt,
|
||||
'[': TokenType.LeftSquareBracket,
|
||||
']': TokenType.RightSquareBracket,
|
||||
'^': TokenType.CircumflexAccent,
|
||||
'_': TokenType.LowLine,
|
||||
'{': TokenType.LeftCurlyBracket,
|
||||
'|': TokenType.VerticalLine,
|
||||
'}': TokenType.RightCurlyBracket,
|
||||
'~': TokenType.Tilde
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a token to the token list
|
||||
* @param {string} type
|
||||
* @param {string} value
|
||||
*/
|
||||
function pushToken(type, value, column) {
|
||||
tokens.push({
|
||||
tn: tn++,
|
||||
ln: ln,
|
||||
col: column,
|
||||
type: type,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character is a decimal digit
|
||||
* @param {string} c Character
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isDecimalDigit(c) {
|
||||
return '0123456789'.indexOf(c) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse spaces
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseSpaces(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a non-space character:
|
||||
for (; pos < css.length; pos++) {
|
||||
if (css.charAt(pos) !== ' ') break;
|
||||
}
|
||||
|
||||
// Add a substring containing only spaces to tokens:
|
||||
pushToken(TokenType.Space, css.substring(start, pos--), col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string within quotes
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
* @param {string} q Quote (either `'` or `"`)
|
||||
*/
|
||||
function parseString(css, q) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a matching quote:
|
||||
for (pos++; pos < css.length; pos++) {
|
||||
// Skip escaped quotes:
|
||||
if (css.charAt(pos) === '\\') pos++;
|
||||
else if (css.charAt(pos) === q) break;
|
||||
}
|
||||
|
||||
// Add the string (including quotes) to tokens:
|
||||
pushToken(q === '"' ? TokenType.StringDQ : TokenType.StringSQ, css.substring(start, pos + 1), col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse numbers
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseDecimalNumber(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet a character that's not a digit:
|
||||
for (; pos < css.length; pos++) {
|
||||
if (!isDecimalDigit(css.charAt(pos))) break;
|
||||
}
|
||||
|
||||
// Add the number to tokens:
|
||||
pushToken(TokenType.DecimalNumber, css.substring(start, pos--), col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse identifier
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseIdentifier(css) {
|
||||
var start = pos;
|
||||
|
||||
// Skip all opening slashes:
|
||||
while (css.charAt(pos) === '/') pos++;
|
||||
|
||||
// Read the string until we meet a punctuation mark:
|
||||
for (; pos < css.length; pos++) {
|
||||
// Skip all '\':
|
||||
if (css.charAt(pos) === '\\') pos++;
|
||||
else if (css.charAt(pos) in Punctuation) break;
|
||||
}
|
||||
|
||||
var ident = css.substring(start, pos--);
|
||||
|
||||
// Enter url mode if parsed substring is `url`:
|
||||
urlMode = urlMode || ident === 'url';
|
||||
|
||||
// Add identifier to tokens:
|
||||
pushToken(TokenType.Identifier, ident, col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse equality sign
|
||||
* @param {string} sass Unparsed part of SASS string
|
||||
*/
|
||||
function parseEquality(css) {
|
||||
pushToken(TokenType.EqualitySign, '==', col);
|
||||
pos++;
|
||||
col++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse inequality sign
|
||||
* @param {string} sass Unparsed part of SASS string
|
||||
*/
|
||||
function parseInequality(css) {
|
||||
pushToken(TokenType.InequalitySign, '!=', col);
|
||||
pos++;
|
||||
col++;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse a multiline comment
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseMLComment(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet `*/`.
|
||||
// Since we already know first 2 characters (`/*`), start reading
|
||||
// from `pos + 2`:
|
||||
for (pos += 2; pos < css.length; pos++) {
|
||||
if (css.charAt(pos) === '*' && css.charAt(pos + 1) === '/') {
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add full comment (including `/*` and `*/`) to the list of tokens:
|
||||
var comment = css.substring(start, pos + 1);
|
||||
pushToken(TokenType.CommentML, comment, col);
|
||||
|
||||
var newlines = comment.split('\n');
|
||||
if (newlines.length > 1) {
|
||||
ln += newlines.length - 1;
|
||||
col = newlines[newlines.length - 1].length;
|
||||
} else {
|
||||
col += (pos - start);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single line comment
|
||||
* @param {string} css Unparsed part of CSS string
|
||||
*/
|
||||
function parseSLComment(css) {
|
||||
var start = pos;
|
||||
|
||||
// Read the string until we meet line break.
|
||||
// Since we already know first 2 characters (`//`), start reading
|
||||
// from `pos + 2`:
|
||||
for (pos += 2; pos < css.length; pos++) {
|
||||
if (css.charAt(pos) === '\n' || css.charAt(pos) === '\r') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add comment (including `//` and line break) to the list of tokens:
|
||||
pushToken(TokenType.CommentSL, css.substring(start, pos--), col);
|
||||
col += pos - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a CSS string to a list of tokens
|
||||
* @param {string} css CSS string
|
||||
* @returns {Array} List of tokens
|
||||
* @private
|
||||
*/
|
||||
function getTokens(css) {
|
||||
// Parse string, character by character:
|
||||
for (pos = 0; pos < css.length; col++, pos++) {
|
||||
c = css.charAt(pos);
|
||||
cn = css.charAt(pos + 1);
|
||||
|
||||
// If we meet `/*`, it's a start of a multiline comment.
|
||||
// Parse following characters as a multiline comment:
|
||||
if (c === '/' && cn === '*') {
|
||||
parseMLComment(css);
|
||||
}
|
||||
|
||||
// If we meet `//` and it is not a part of url:
|
||||
else if (!urlMode && c === '/' && cn === '/') {
|
||||
// If we're currently inside a block, treat `//` as a start
|
||||
// of identifier. Else treat `//` as a start of a single-line
|
||||
// comment:
|
||||
parseSLComment(css);
|
||||
}
|
||||
|
||||
// If current character is a double or single quote, it's a start
|
||||
// of a string:
|
||||
else if (c === '"' || c === "'") {
|
||||
parseString(css, c);
|
||||
}
|
||||
|
||||
// If current character is a space:
|
||||
else if (c === ' ') {
|
||||
parseSpaces(css);
|
||||
}
|
||||
|
||||
// If current character is `=`, it must be combined with next `=`
|
||||
else if (c === '=' && cn === '=') {
|
||||
parseEquality(css);
|
||||
}
|
||||
|
||||
// If we meet `!=`, this must be inequality
|
||||
else if (c === '!' && cn === '=') {
|
||||
parseInequality(css);
|
||||
}
|
||||
|
||||
// If current character is a punctuation mark:
|
||||
else if (c in Punctuation) {
|
||||
// Add it to the list of tokens:
|
||||
pushToken(Punctuation[c], c, col);
|
||||
if (c === '\n' || c === '\r') {
|
||||
ln++;
|
||||
col = 0;
|
||||
} // Go to next line
|
||||
if (c === ')') urlMode = false; // exit url mode
|
||||
if (c === '{') blockMode++; // enter a block
|
||||
if (c === '}') blockMode--; // exit a block
|
||||
}
|
||||
|
||||
// If current character is a decimal digit:
|
||||
else if (isDecimalDigit(c)) {
|
||||
parseDecimalNumber(css);
|
||||
}
|
||||
|
||||
// If current character is anything else:
|
||||
else {
|
||||
parseIdentifier(css);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
return getTokens(css);
|
||||
};
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
module.exports = {
|
||||
StringSQ: 'StringSQ',
|
||||
StringDQ: 'StringDQ',
|
||||
CommentML: 'CommentML',
|
||||
CommentSL: 'CommentSL',
|
||||
|
||||
Newline: 'Newline',
|
||||
Space: 'Space',
|
||||
Tab: 'Tab',
|
||||
|
||||
ExclamationMark: 'ExclamationMark', // !
|
||||
QuotationMark: 'QuotationMark', // "
|
||||
NumberSign: 'NumberSign', // #
|
||||
DollarSign: 'DollarSign', // $
|
||||
PercentSign: 'PercentSign', // %
|
||||
Ampersand: 'Ampersand', // &
|
||||
Apostrophe: 'Apostrophe', // '
|
||||
LeftParenthesis: 'LeftParenthesis', // (
|
||||
RightParenthesis: 'RightParenthesis', // )
|
||||
Asterisk: 'Asterisk', // *
|
||||
PlusSign: 'PlusSign', // +
|
||||
Comma: 'Comma', // ,
|
||||
HyphenMinus: 'HyphenMinus', // -
|
||||
FullStop: 'FullStop', // .
|
||||
Solidus: 'Solidus', // /
|
||||
Colon: 'Colon', // :
|
||||
Semicolon: 'Semicolon', // ;
|
||||
LessThanSign: 'LessThanSign', // <
|
||||
EqualsSign: 'EqualsSign', // =
|
||||
EqualitySign: 'EqualitySign', // ==
|
||||
InequalitySign: 'InequalitySign', // !=
|
||||
GreaterThanSign: 'GreaterThanSign', // >
|
||||
QuestionMark: 'QuestionMark', // ?
|
||||
CommercialAt: 'CommercialAt', // @
|
||||
LeftSquareBracket: 'LeftSquareBracket', // [
|
||||
ReverseSolidus: 'ReverseSolidus', // \
|
||||
RightSquareBracket: 'RightSquareBracket', // ]
|
||||
CircumflexAccent: 'CircumflexAccent', // ^
|
||||
LowLine: 'LowLine', // _
|
||||
LeftCurlyBracket: 'LeftCurlyBracket', // {
|
||||
VerticalLine: 'VerticalLine', // |
|
||||
RightCurlyBracket: 'RightCurlyBracket', // }
|
||||
Tilde: 'Tilde', // ~
|
||||
|
||||
Identifier: 'Identifier',
|
||||
DecimalNumber: 'DecimalNumber'
|
||||
};
|
||||
Reference in New Issue
Block a user