cleanup, smaller chunks, speed improvements

This commit is contained in:
cloudhead
2010-06-25 19:58:25 -04:00
parent 77fa4a171c
commit 369b978c78

View File

@@ -10,22 +10,16 @@ if (typeof(window) === 'undefined') {
//
// less.js - parser
//
// A relatively straight-forward recursive-descent parser.
// A relatively straight-forward predictive parser.
// There is no tokenization/lexing stage, the input is parsed
// in one sweep.
//
// To make the parser fast enough to run in the browser, several
// optimization had to be made:
//
// - Instead of the more commonly used technique of slicing the
// input string on every match, we use global regexps (/g),
// and move the `lastIndex` pointer on match, foregoing `slice()`
// completely. This gives us a 3x speed-up.
//
// - Matching on a huge input is often cause of slowdowns,
// especially with the /g flag. The solution to that is to
// chunkify the input: we split it by /\n\n/, just to be on
// the safe side. The chunks are stored in the `chunks` var,
// - Matching and slicing on a huge input is often cause of slowdowns.
// The solution is to chunkify the input into smaller strings.
// The chunks are stored in the `chunks` var,
// `j` holds the current chunk index, and `current` holds
// the index of the current chunk in relation to `input`.
// This gives us an almost 4x speed-up.
@@ -50,9 +44,8 @@ less.Parser = function Parser(env) {
var input, // LeSS input string
i, // current index in `input`
j, // current chunk
temp,
memo,
chunk,
temp, // temporarily holds a chunk's state, for backtracking
memo, // temporarily holds `i`, when backtracking
furthest, // furthest index the parser has gone to
chunks, // chunkified input
current, // index of current chunk, in `input`
@@ -87,19 +80,12 @@ less.Parser = function Parser(env) {
}
};
function save(){
temp = chunk;
memo = i;
current = i;
}
function restore() {
chunks[j] = chunk = temp;
i = memo;
current = i;
}
function save() { temp = chunks[j], memo = i, current = i }
function restore() { chunks[j] = temp, i = memo, current = i }
function sync() {
if (i > current) {
chunks[j] = chunk = chunk.slice(i - current);
chunks[j] = chunks[j].slice(i - current);
current = i;
}
}
@@ -136,11 +122,11 @@ less.Parser = function Parser(env) {
} else {
sync ();
match = tok.exec(chunk);
if (match) { // 3.
if (match = tok.exec(chunks[j])) { // 3.
length = match[0].length;
} else { return }
} else {
return null;
}
}
// The match is confirmed, add the match length to `i`,
@@ -149,8 +135,7 @@ less.Parser = function Parser(env) {
// grammar is mostly white-space insensitive.
//
if (match) {
i += length;
var mem = i;
mem = i += length;
endIndex = i + chunk.length - length;
while (i < endIndex) {
@@ -158,10 +143,10 @@ less.Parser = function Parser(env) {
if (! (c === 32 || c === 10 || c === 9)) { break }
i++;
}
chunks[j] = chunk = chunk.slice(length + (i - mem));
chunks[j] = chunks[j].slice(length + (i - mem));
current = i;
if (chunk.length === 0 && j < chunks.length - 1) { chunk = chunks[++j] }
if (chunks[j].length === 0 && j < chunks.length - 1) { j++ }
if(typeof(match) === 'string') {
return match;
@@ -177,7 +162,7 @@ less.Parser = function Parser(env) {
if (typeof(tok) === 'string') {
return input.charAt(i) === tok;
} else {
if (tok.test(chunk)) {
if (tok.test(chunks[j])) {
return true;
} else {
return false;
@@ -185,14 +170,6 @@ less.Parser = function Parser(env) {
}
}
function exec(tok) {
var match;
if ((match = tok.exec(chunks[j]))) {
return match;
}
}
this.env = env || {};
// The optimization level dictates the thoroughness of the parser,
@@ -223,13 +200,10 @@ less.Parser = function Parser(env) {
// removing comments (see rationale above),
// depending on the level of optimization.
if (that.optimization > 0) {
input = input.replace(/\/\*(?:[^*]|\*+[^\/*])*\*+\//g, function (comment) {
return that.optimization > 1 ? '' : comment.replace(/\n(\s*\n)+/g, '\n');
});
if (that.optimization > 1) {
input = input.replace(/\/\*(?:[^*]|\*+[^\/*])*\*+\//g, '');
chunks = (function (chunks) {
var level = 0,
j = 0,
var j = 0,
skip = /[^"'\{\}]+/g,
match,
chunk,
@@ -249,15 +223,10 @@ less.Parser = function Parser(env) {
c = input.charAt(i);
if (c === '}' && !inString) {
level --;
chunk.push(c);
if (level === 0) {
chunks[++j] = [];
}
chunks[++j] = [];
} else {
if (c === '{' && !inString) {
level ++;
} else if (c === '"' || c === "'") {
if (c === '"' || c === "'") {
inString = inString === c ? false : c;
}
chunk.push(c);
@@ -272,7 +241,6 @@ less.Parser = function Parser(env) {
chunks = [input];
}
inputLength = input.length;
chunk = chunks[0];
// Start with the primary rule.
// The whole syntax tree is held under a Ruleset node,
@@ -819,7 +787,7 @@ less.Parser = function Parser(env) {
var selectors = [], s, rules, match;
save();
if (match = exec(/^([.#: \w-]+)[\s\n]*\{/)) {
if (match = /^([.#: \w-]+)[\s\n]*\{/.exec(chunks[j])) {
i += match[0].length - 1;
selectors = [new(tree.Selector)([new(tree.Element)(null, match[1])])];
} else {
@@ -845,7 +813,7 @@ less.Parser = function Parser(env) {
if (c === '.' || c === '#' || c === '&') { return }
if (name = $(this.variable) || $(this.property)) {
if ((name.charAt(0) != '@') && (match = exec(/^([^@+\/*(;{}-]*);/))) {
if ((name.charAt(0) != '@') && (match = /^([^@+\/*(;{}-]*);/.exec(chunks[j]))) {
i += match[0].length - 1;
value = new(tree.Anonymous)(match[1]);
} else if (name === "font") {