Regexp vs Parser
JavaScript performance comparison
Info
Comparing a parser written with regexp's with a proper parser.
Preparation code
<script>
Benchmark.prototype.setup = function() {
var css = '';
for (var i = 100; i > 0; i--) {
css += "h1 {\n color: red;\n }\n\n .some-class {\n color: #ff0;\n background: #f00;\n\n .nested {\n color: blue;\n\n .another {\n background: pink;\n }\n }\n }\n\n"
}
var Rule,
openRe = /((([\*\@\#\.\w\d])([\s\*\@\#\.\w\d\-\,\>\:\=\"\~\^\$\(\)\+]*))\{)\s*/,
closeRe = /\s*\}/;
Rule = (function() {
function Rule(source, selector, start, declarationStart, end, declarationEnd) {
this.source = source;
this.selector = selector;
this.start = start;
this.declarationStart = declarationStart;
this.end = end || null;
this.declarationEnd = declarationEnd || null;
this.nested = [];
}
return Rule;
}());
function regexParser(css) {
var length = css.length, stack = [], context, nested, match, open, close, rule, rules;
this.cssText = css;
this.rules = rules = context = [];
while (css) {
open = css.indexOf('{');
close = css.indexOf('}');
if (open >= 0 && (close == -1 || open < close) && (match = css.match(openRe))) {
css = css.slice(open + 1);
rule = new Rule(this.cssText, match[2].trim(),
length - css.length - match[1].length, length - css.length);
context.push(rule);
stack.push(context);
context = rule.nested;
} else if (close >= 0 && (open == -1 || close < open) && (match = css.match(closeRe))) {
css = css.slice(close + match.length);
context = stack.pop();
rule = context[context.length - 1];
if (rule) {
rule.end = length - css.length;
rule.declarationEnd = rule.end - match.length;
} else {
console.log('Closing unopened rule');
}
} else {
css = null;
}
}
// Pop remaining
while (stack.length) {
context = stack.pop();
rule = context[context.length - 1];
rule.end = rule.declarationEnd = length;
console.log("Unclosed rule: '" + rule.selector + "'");
}
return rules;
}
function realParserWithCase(css) {
var stack = [], rules = [], context = rules, state = 'before-selector',
buffer = 0, index = 0, rule, char, start;
this.cssText = css;
this.rules = rules;
while (char = css.charAt(index++)) {
switch(char) {
case ' ': case '\t': case '\r': case '\n': case '\f':
if (state === 'selector') { buffer++; }
break;
case '{':
if (state !== 'selector') { break; }
start = index - buffer - 1;
rule = new Rule(css, css.slice(start, index - 1).trim(), start, index);
context.push(rule);
stack.push(context);
context = rule.nested;
state = 'before-selector'
buffer = 0;
break;
case ';':
if (state !== 'selector') { break; }
state = 'before-selector';
buffer = 0;
break;
case '}':
if (state !== 'before-selector') { break; }
context = stack.pop();
rule = context[context.length - 1];
if (rule) {
rule.end = index;
rule.declarationEnd = index - 1;
} else {
console.log('Closing unopened rule');
}
state = "before-selector";
buffer = 0;
break;
default:
if (state === 'before-selector') { state = 'selector'; }
buffer++;
break;
}
}
// Pop remaining
while (stack.length) {
context = stack.pop();
rule = context[context.length - 1];
rule.end = rule.declarationEnd = css.length;
console.log("Unclosed rule: '" + rule.selector + "'");
}
return rules;
}
function realParserWithIf(css) {
var stack = [], rules = [], context = rules, state = 'before-selector',
buffer = 0, index = 0, rule, char, start;
this.cssText = css;
this.rules = rules;
while (char = css.charAt(index++)) {
if (char === ' ' || char === '\t' || char === '\r' || char === '\n' || char === '\f') {
if (state === 'selector') { buffer++; }
} else if (char === '{') {
if (state === 'selector') {
start = index - buffer - 1;
rule = new Rule(css, css.slice(start, index - 1).trim(), start, index);
context.push(rule);
stack.push(context);
context = rule.nested;
state = 'before-selector'
buffer = 0;
}
} else if (char === ';') {
if (state === 'selector') {
state = 'before-selector';
buffer = 0;
}
} else if (char === '}') {
if (state === 'before-selector') {
context = stack.pop();
rule = context[context.length - 1];
if (rule) {
rule.end = index;
rule.declarationEnd = index - 1;
} else {
console.log('Closing unopened rule');
}
state = "before-selector";
buffer = 0;
}
} else {
if (state === 'before-selector') { state = 'selector'; }
buffer++;
}
}
// Pop remaining
while (stack.length) {
context = stack.pop();
rule = context[context.length - 1];
rule.end = rule.declarationEnd = css.length;
console.log("Unclosed rule: '" + rule.selector + "'");
}
return rules;
}
function realParserWithLookup(css) {
var stack = [], rules = [], context = rules, state = 'before-selector',
buffer = 0, index = 0, rule, char, start,
open = {
selector: function() {
start = index - buffer - 1;
rule = new Rule(css, css.slice(start, index - 1).trim(), start, index);
context.push(rule);
stack.push(context);
context = rule.nested;
state = 'before-selector'
buffer = 0;
},
"before-selector": function() {}
},
close = {
"before-selector": function() {
context = stack.pop();
rule = context[context.length - 1];
if (rule) {
rule.end = index;
rule.declarationEnd = index - 1;
} else {
console.log('Closing unopened rule');
}
state = "before-selector";
buffer = 0;
},
selector: function() {}
},
endRule = {
"before-selector": function() {},
selector: function() {
state = 'before-selector';
buffer = 0;
}
};
this.cssText = css;
this.rules = rules;
while (char = css.charAt(index++)) {
switch(char) {
case ' ': case '\t': case '\r': case '\n': case '\f':
if (state === 'selector') { buffer++; }
break;
case '{':
open[state]();
break;
case ';':
endRule[state]();
break;
case '}':
close[state]();
break;
default:
if (state === 'before-selector') { state = 'selector'; }
buffer++;
break;
}
}
// Pop remaining
while (stack.length) {
context = stack.pop();
rule = context[context.length - 1];
rule.end = rule.declarationEnd = css.length;
console.log("Unclosed rule: '" + rule.selector + "'");
}
return rules;
}
};
Benchmark.prototype.teardown = function() {
Rule = null;
};
</script>
Test runner
Warning! For accurate results, please disable Firebug before running the tests. (Why?)
Java applet disabled.
| Test | Ops/sec | |
|---|---|---|
Regexp Parser |
|
pending… |
Real Parser with "case" |
|
pending… |
Real Parser with "if" |
|
pending… |
Real Parser with Lookup |
|
pending… |
Compare results of other browsers
Revisions
You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:
- Revision 1: published by Jamie Hill
- Revision 2: published by Jamie Hill
- Revision 4: published
- Revision 5: published by Jamie Hill
- Revision 6: published by Jamie Hill
- Revision 7: published by Jamie Hill
0 comments