first commit

This commit is contained in:
2019-09-30 16:21:51 +02:00
commit 5f00dd9b15
9746 changed files with 905481 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
*.iml
.DS_Store
GNUmakefile
.idea/
benchmark/
log/
src/
test/
+10
View File
@@ -0,0 +1,10 @@
language: node_js
node_js:
- "0.11"
- "0.10"
- "0.8"
matrix:
allow_failures:
- node_js: "0.11"
+120
View File
@@ -0,0 +1,120 @@
Gonzales changelog
==================
xx.xx.201x, Version 3.0.0
-------------------------
AST:
- Rename `interpolatedVariable` to `interpolation`
- Rename `functionBody` to `arguments`
- New node type `conditionalStatement`
- Remove unused node types
Parsing rules:
- Interpolations are ok as arguments, inside nth selector and within identifiers
- Functions are ok as arguments
- Spaces are optional after declDelim
- Check for unary operator in arguments
- Parse operators inside arguments (#16)
- Remove spaces from the end of includes
- Every argument can be escaped string
- Braces around arguments are ok
- Equality and inequality signs are ok as operators
API:
- srcToAST({src: 'string', syntax: 'css', needInfo: true, rule: 'stylesheet'})
- astToSrc()
- astToString()
Tests:
- Add more tests
- Place test files inside rule directory
- Use 'min' reporter instead of 'spec'
- Add Travis config
- Divide log and test scripts
29.12.2013, Version 2.0.2
-------------------------
- Sass includes can have both arguments list and content block,
i.e. `@include nani() { color: tomato }` is valid syntax.
18.11.2013, Version 2.0.1
-------------------------
- Bring back lost whitespaces and comments
11.11.2013, Version 2.0.0
-------------------------
- Support preprocessors: Sass (both SCSS and indented syntax), LESS.
- New node types:
- `arguments` (less and sass only)
- `commentML`
- `commentSL` (less and sass only)
- `condition` (sass only)
- `default` (sass only)
- `escapedString` (less only)
- `include` (less and sass only)
- `loop` (sass only)
- `mixin` (less and sass only)
- `parentselector` (less and sass only)
- `placeholder` (sass only)
- `propertyDelim`
- `variable` (less and sass only)
- `varialeList` (less and sass only)
- Rename methods:
- `srcToCSSP` -> `cssToAST`
- `csspToSrc` -> `astToCSS`
- `csspToTree` -> `astToTree`
- Pass all arguments as one object:
- `gonzales.cssToAST({css: a, syntax: b, rule: c, needInfo: d})`
- `gonzales.astToCSS({ast: ast, syntax: syntax})`
- Remove built files from VCS
- Move test command from `make` to `npm`
- Build files before running tests
- Divide tests into groups according to syntax
- Add script to test one specific css string
- Add token's index number to info object
11.02.2013, Version 1.0.7
-------------------------
- Identifiers like `_0` are identifiers now.
- Throw error instead of console.error: https://github.com/css/csso/issues/109
25.11.2012, Version 1.0.6
-------------------------
- Typo fix (global variable leak): https://github.com/css/csso/pull/110
- Attribute selectors extended by `|`.
- `not(..)` pseudo-class special support: https://github.com/css/csso/issues/111
28.10.2012, Version 1.0.5
-------------------------
- Better error line numbering: https://github.com/css/gonzales/issues/2
11.10.2012, Version 1.0.4
-------------------------
- CSSO issue (@page inside @media error): https://github.com/css/csso/issues/90
10.10.2012, Version 1.0.3
-------------------------
- Both .t-1 and .t-01 should be idents: https://github.com/css/gonzales/issues/1
08.10.2012, Version 1.0.2
-------------------------
- CSSO issue (filter + important breaks csso v1.3.1): https://github.com/css/csso/issues/87
08.10.2012, Version 1.0.1
-------------------------
- CSSO issue ("filter" IE property breaks CSSO v1.3.0): https://github.com/css/csso/issues/86
03.10.2012, Version 1.0.0
-------------------------
- First revision.
+19
View File
@@ -0,0 +1,19 @@
Copyright (C) 2012 by Sergey Kryzhanovsky
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+227
View File
@@ -0,0 +1,227 @@
## API
### gonzales.parse(css, options)
Parse CSS.
Parameters:
* `{String} css`
* `{{syntax: String, rule: String}} options`
Returns:
* `{Object} ast`.
Example:
```js
var css = 'a {color: tomato}';
var ast = gonzales.parse(css);
```
Example:
```js
var less = 'a {$color: tomato}';
var ast = gonzales.parse(less, {syntax: 'less'});
```
Example:
```js
var less = '$color: tomato';
var ast = gonzales.parse(less, {syntax: 'less', rule: 'declaration'});
```
### gonzales.createNode(options)
Creates a new node.
Parameters:
* `{{type: String, content: String|Array}} options`
Returns:
* `{Object} ast`
Example:
```js
var css = 'a {color: tomato}';
var ast = gonzales.parse(css);
var node = gonzales.createNode({ type: 'animal', content: 'panda' });
ast.content.push(node);
```
### ast.length
### ast.toString()
### ast.toCSS(syntax)
Converts AST to code.
Parameters:
* `{String} syntax`
Returns:
* `{String} css`
Example:
```js
var css = ast.toCSS('css');
var less = ast.toCSS('less');
```
### ast.contains(type)
Checks whether there is a child node of given type.
Parameters:
* `{String} type`
Returns:
* `{Boolean}`
Example:
```js
if (ast.contains('panda'))
doSomething();
```
### ast.first(type)
Returns the first child node of given type.
Parameters:
* `{String=} type`
Returns:
* `{Node} node`
Example:
```js
var node = ast.first();
node.content = 'panda';
```
Example:
```js
var node = ast.first('commentML');
node.content = 'panda';
```
### ast.forEach(type, function)
Calls the function for every child node of given type.
Parameters:
* `{String=} type`
* `{Function} function`
Example:
```js
ast.forEach('commentML', function(node) {
node.content = 'panda';
});
```
### ast.get(index)
### ast.insert(index, node)
### ast.is(type)
Checks whether the node is of given type.
Parameters:
* `{String} type`
Returns:
* `{Boolean}`
Example:
```js
if (ast.is('s'))
ast.content = '';
```
### ast.last(type)
Returns the last child node of given type.
Parameters:
* `{String=} type`
Returns:
* `{Node} node`
Example:
```js
var node = ast.last()
node.content = 'panda';
```
Example:
```js
var node = ast.last('commentML');
node.content = 'panda';
```
### ast.map(function)
Calls the function for every node in a tree. Modifies the tree!
Parameters:
* `{Function} function`
Example:
```js
ast.map(function(node) {
if (node.type === 'commentML') node.content = 'panda';
});
```
## Test
To run tests:
npm test
This command will build library files from sources and run tests on all files
in syntax directories.
Every test has 3 files: source stylesheet, expected AST and expected string
compiled back from AST to css.
If some tests fail, you can find information in test logs:
- `log/test.log` contains all information from stdout;
- `log/expected.txt` contains only expected text;
- `log/result.txt` contains only result text.
The last two are made for your convenience: you can use any diff app to see
the defference between them.
If you want to test one specific string or get a general idea of how Gonzales
works, you can use `test/ast.js` file.
Simply change the first two strings (`css` and `syntax` vars) and run:
node test/single-test.js
## Report
If you find a bug or want to add a feature, welcome to [Issues](https://github.com/tonyganch/gonzales-pe/issues).
If you are shy but have a question, feel free to [drop me a
line](mailto:tonyganch+gonzales@gmail.com).
+26
View File
@@ -0,0 +1,26 @@
Tests:
+ Проверить, что все тесты, которые есть в папках, фактически запускаются
- Починить pending тесты
Fix:
- Fix column/line position
- Replace `content: null` with actual content
- Replace `unary` with `operator`
- Вынести пробелы из начала atrulerq
- Проверить functionExpression: type string or array
- Убрать: Внутри ident в sass могут быть interpolations, поэтому ident.content является массивом из строк и interpolations
- Типы вроде `important` в качестве контента должны содержать не пробелы, а весь идент
- Убрать unknown type
- Сделать вспомогательные методы вроде blockDecl приватными
- В Sass убрать curly braces у atruler
Docs:
- Changelog
- Readme
- AST
- API
Плюшки:
- Попробовать Closure Compiler и сравнить скорость
- Update bin
- Вынести тестовые синтаксисы в отд. репозиторий-модуль
@@ -0,0 +1,45 @@
#!/usr/bin/env node
/**
* Generate `.json` test files.
* Usage: `./bin/generate-tests.js <syntax name> [<syntax name>]`.
* Example: `./bin/generate-tests.js scss less`.
*/
var gonzales = require('..'),
fs = require('fs'),
path = require('path'),
syntaxList = process.argv.slice(2);
syntaxList.forEach(function (s) {
var syntaxDir = 'test/' + s;
var extension = '.' + s;
fs.readdirSync(syntaxDir).forEach(function(testDir) {
fs.readdirSync(path.join(syntaxDir, testDir)).forEach(function(file) {
if (path.extname(file) !== extension) return;
try {
generateTest(path.join(syntaxDir, testDir, file));
} catch (e) {
console.log('*', path.join(syntaxDir, testDir, file));
}
});
});
});
function generateTest(filePath) {
var testDir = path.dirname(filePath);
var rule = path.basename(testDir);
var syntax = path.basename(path.dirname(testDir));
var fileName = path.basename(filePath, '.' + syntax);
var inputPath = path.join(testDir, fileName + '.' + syntax);
var outputPath = path.join(testDir, fileName + '.json');
var string = fs.readFileSync(inputPath, 'utf-8').trim();
var ast = gonzales.parse(string, {syntax: syntax, rule: rule});
fs.writeFileSync(outputPath, ast.toString());
}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env node
/**
* ./bin/gonzales.js filename
* ./bin/gonzales.js filename -s
*/
var gonzales = require('..'),
fs = require('fs'),
path = require('path'),
filename = process.argv[2],
silent = process.argv[3] === '-s';
if (!filename) {
console.log('Please supply a filename. Usage "gonzales file"');
process.exit();
}
var syntax = path.extname(filename).substring(1);
var css = fs.readFileSync(filename, 'utf-8');
try {
var ast = gonzales.parse(css, {syntax: syntax});
if (!silent) console.log(ast.toString());
} catch (e) {
if (!silent) throw e;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,148 @@
### Gonzales usage
#### 1. Sample
With the help of this sample, you can check if Gonzales is installed correctly and if its (three) functions are working properly.
It is assumed that in the production code you will change AST in a more intelligent way.
Sample code:
var gonzales = require('gonzales-pe'),
src = 'a { color: red }',
ast = gonzales.cssToAST(src);
console.log('== CSS:');
console.log(src);
console.log('\n== CSS -> AST:');
console.log(gonzales.astToTree(ast));
ast[1][1][1][1][1] = 'b';
console.log('\n== AST\':');
console.log(gonzales.astToTree(ast));
console.log('\n== AST\' -> CSS:');
console.log(gonzales.astToCSS(ast));
Result:
== CSS:
a { color: red }
== CSS -> AST:
['stylesheet',
['ruleset',
['selector',
['simpleselector',
['ident', 'a'],
['s', ' ']]],
['block',
['s', ' '],
['declaration',
['property',
['ident', 'color']],
['propertyDelim'],
['value',
['s', ' '],
['ident', 'red'],
['s', ' ']]]]]]
== AST':
['stylesheet',
['ruleset',
['selector',
['simpleselector',
['ident', 'b'],
['s', ' ']]],
['block',
['s', ' '],
['declaration',
['property',
['ident', 'color']],
['propertyDelim'],
['value',
['s', ' '],
['ident', 'red'],
['s', ' ']]]]]]
== AST' -> CSS:
b { color: red }
#### 2. API
In Node.js you can use Gonzales module this way:
```
gonzales = require('gonzales-pe');
```
You can use AST through the next functions.
##### CSS -> AST
It parses source style to AST:
```
gonzales.cssToAST({
css: css,
rule: rule,
needInfo: true,
syntax: 'scss'
});
```
where:
- `css` — a string with the CSS style;
- `rule` — a string with the token type in case the style is not complete
(`'stylesheet'` is default);
- `needInfo` — whether to include info object into AST (`false` is default);
- `syntax` — a string with syntax name (`css` is default, other supported
syntaxes are: `scss`, `sass` and `less`).
Example 1: if you want to parse only *declaration*, you have to call:
```
gonzales.cssToAST({
css: 'color: red',
rule: 'declaration'
});
```
Example 2: in case the style is complete and you don't need an info object,
the call is shortned to:
```
gonzales.cssToAST(css);
```
Example 3: if you want to parse a sass file:
```
gonzales.cssToAST({
css: css,
syntax: 'sass'
});
```
##### AST -> CSS
Translates AST to style:
```
gonzales.astToCSS({
ast: ast,
syntax: 'scss'
})
```
where:
- `ast` — AST to be translated;
- `syntax` — a string with syntax name (`css` is default).
##### AST -> TREE
Translates AST to the string representation of the tree:
```
gonzales.astToTree(ast);
```
where:
- `ast` — AST to be translated.
This function is useful for debugging or learning purposes.
+129
View File
@@ -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;
File diff suppressed because it is too large Load Diff
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
})();
File diff suppressed because it is too large Load Diff
@@ -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);
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
@@ -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
View File
@@ -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);
}
})();
File diff suppressed because it is too large Load Diff
@@ -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);
}
@@ -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
View File
@@ -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);
}
})();
File diff suppressed because it is too large Load Diff
@@ -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);
}
@@ -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
View File
@@ -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'
};
+64
View File
@@ -0,0 +1,64 @@
{
"_args": [
[
"gonzales-pe@3.0.0-28",
"/mnt/data/Sites/r2c.net/user/themes/r2c"
]
],
"_development": true,
"_from": "gonzales-pe@3.0.0-28",
"_id": "gonzales-pe@3.0.0-28",
"_inBundle": false,
"_integrity": "sha1-3VC0HdFbaCooxA5fD/IAeQGsYr0=",
"_location": "/gonzales-pe",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "gonzales-pe@3.0.0-28",
"name": "gonzales-pe",
"escapedName": "gonzales-pe",
"rawSpec": "3.0.0-28",
"saveSpec": null,
"fetchSpec": "3.0.0-28"
},
"_requiredBy": [
"/csscomb",
"/csscomb-core"
],
"_resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-3.0.0-28.tgz",
"_spec": "3.0.0-28",
"_where": "/mnt/data/Sites/r2c.net/user/themes/r2c",
"author": {
"name": "Tony Ganch",
"email": "tonyganch+github@gmail.com",
"url": "http://tonyganch.com"
},
"bin": {
"gonzales": "./bin/gonzales.js"
},
"bugs": {
"url": "http://github.com/tonyganch/gonzales-pe/issues"
},
"description": "Gonzales Preprocessor Edition (fast CSS parser)",
"devDependencies": {
"coffee-script": "~1.7.1",
"mocha": "~1.20.0"
},
"engines": {
"node": ">=0.6.0"
},
"homepage": "http://github.com/tonyganch/gonzales-pe",
"license": "MIT",
"main": "./lib/gonzales",
"name": "gonzales-pe",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/tonyganch/gonzales-pe.git"
},
"scripts": {
"log": "(mkdir -p log && node ./test/mocha.js) | tee ./log/test.log",
"test": "node ./test/mocha.js"
},
"version": "3.0.0-28"
}