first commit
This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
||||
*.iml
|
||||
.DS_Store
|
||||
GNUmakefile
|
||||
.idea/
|
||||
benchmark/
|
||||
log/
|
||||
src/
|
||||
test/
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "0.11"
|
||||
- "0.10"
|
||||
- "0.8"
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- node_js: "0.11"
|
||||
+120
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
- Вынести тестовые синтаксисы в отд. репозиторий-модуль
|
||||
+45
@@ -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
@@ -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;
|
||||
}
|
||||
+1214
File diff suppressed because it is too large
Load Diff
+148
@@ -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
@@ -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'
|
||||
};
|
||||
+64
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user