+/* jshint latedef: false */
+
module.exports = function Tokenizer(data) {
+ var chunker = new Chunker(data, 128);
+ var chunk = chunker.next();
+
var whatsNext = function(context) {
var cursor = context.cursor;
var mode = context.mode;
var closest;
+ if (chunk.length == context.cursor) {
+ if (chunker.isEmpty())
+ return null;
+
+ chunk = chunker.next();
+ context.cursor = 0;
+ }
+
if (mode == 'body') {
- closest = data.indexOf('}', cursor);
+ closest = chunk.indexOf('}', cursor);
return closest > -1 ?
[closest, 'bodyEnd'] :
null;
}
- var nextSpecial = data.indexOf('@', cursor);
- var nextEscape = mode == 'top' ? data.indexOf('__ESCAPED_COMMENT_CLEAN_CSS', cursor) : -1;
- var nextBodyStart = data.indexOf('{', cursor);
- var nextBodyEnd = data.indexOf('}', cursor);
+ var nextSpecial = chunk.indexOf('@', context.cursor);
+ var nextEscape = mode == 'top' ? chunk.indexOf('__ESCAPED_COMMENT_CLEAN_CSS', context.cursor) : -1;
+ var nextBodyStart = chunk.indexOf('{', context.cursor);
+ var nextBodyEnd = chunk.indexOf('}', context.cursor);
closest = nextSpecial;
if (closest == -1 || (nextEscape > -1 && nextEscape < closest))
while (true) {
var next = whatsNext(context);
if (!next) {
- var whatsLeft = data.substring(context.cursor);
+ var whatsLeft = chunk.substring(context.cursor);
if (whatsLeft.length > 0) {
tokenized.push(whatsLeft);
context.cursor += whatsLeft.length;
var oldMode;
if (what == 'special') {
- var fragment = data.substring(nextSpecial, context.cursor + '@font-face'.length + 1);
+ var fragment = chunk.substring(nextSpecial, context.cursor + '@font-face'.length + 1);
var isSingle = fragment.indexOf('@import') === 0 || fragment.indexOf('@charset') === 0;
if (isSingle) {
- nextEnd = data.indexOf(';', nextSpecial + 1);
- tokenized.push(data.substring(context.cursor, nextEnd + 1));
+ nextEnd = chunk.indexOf(';', nextSpecial + 1);
+ tokenized.push(chunk.substring(context.cursor, nextEnd + 1));
context.cursor = nextEnd + 1;
} else {
- nextEnd = data.indexOf('{', nextSpecial + 1);
- var block = data.substring(context.cursor, nextEnd).trim();
+ nextEnd = chunk.indexOf('{', nextSpecial + 1);
+ var block = chunk.substring(context.cursor, nextEnd).trim();
var isFlat = fragment.indexOf('@font-face') === 0;
oldMode = context.mode;
tokenized.push({ block: block, body: specialBody });
}
} else if (what == 'escape') {
- nextEnd = data.indexOf('__', nextSpecial + 1);
- var escaped = data.substring(context.cursor, nextEnd + 2);
+ nextEnd = chunk.indexOf('__', nextSpecial + 1);
+ var escaped = chunk.substring(context.cursor, nextEnd + 2);
tokenized.push(escaped);
context.cursor = nextEnd + 2;
} else if (what == 'bodyStart') {
- var selector = data.substring(context.cursor, nextSpecial).trim();
+ var selector = chunk.substring(context.cursor, nextSpecial).trim();
oldMode = context.mode;
context.cursor = nextSpecial + 1;
}
if (context.mode != 'block')
- tokenized = data.substring(context.cursor, nextSpecial);
+ tokenized = chunk.substring(context.cursor, nextSpecial);
context.cursor = nextSpecial + 1;
}
};
};
+
+// Divides `data` into chunks of `chunkSize` for faster processing
+var Chunker = function(data, chunkSize) {
+ var chunks = [];
+ for (var cursor = 0, dataSize = data.length; cursor < dataSize;) {
+ var nextCursor = cursor + chunkSize > dataSize ?
+ dataSize - 1 :
+ cursor + chunkSize;
+
+ if (data[nextCursor] != '}')
+ nextCursor = data.indexOf('}', nextCursor);
+ if (nextCursor == -1)
+ nextCursor = data.length - 1;
+
+ chunks.push(data.substring(cursor, nextCursor + 1));
+ cursor = nextCursor + 1;
+ }
+
+ return {
+ isEmpty: function() {
+ return chunks.length === 0;
+ },
+
+ next: function() {
+ return chunks.shift() || '';
+ }
+ };
+};