ee8c121676
Labeled expressions lead to more maintainable code and also will allow certain optimizations (we can ignore results of expressions not passed to the actions). This does not speed up the benchmark suite execution statistically significantly on V8. Detailed results (benchmark suite totals): --------------------------------- Test # Before After --------------------------------- 1 28.43 kB/s 28.46 kB/s 2 28.38 kB/s 28.56 kB/s 3 28.22 kB/s 28.58 kB/s 4 28.76 kB/s 28.55 kB/s 5 28.57 kB/s 28.48 kB/s --------------------------------- Average 28.47 kB/s 28.53 kB/s --------------------------------- Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.55 Safari/533.4
555 lines
13 KiB
JavaScript
555 lines
13 KiB
JavaScript
/*
|
|
* CSS parser based on the grammar described at http://www.w3.org/TR/CSS2/grammar.html.
|
|
*
|
|
* The parser builds a tree representing the parsed CSS, composed of basic
|
|
* JavaScript values, arrays and objects (basically JSON). It can be easily
|
|
* used by various CSS processors, transformers, etc.
|
|
*
|
|
* Note that the parser does not handle errors in CSS according to the
|
|
* specification -- many errors which it should recover from (e.g. malformed
|
|
* declarations or unexpected end of stylesheet) are simply fatal. This is a
|
|
* result of straightforward rewrite of the CSS grammar to PEG.js and it should
|
|
* be fixed sometimes.
|
|
*/
|
|
|
|
/* ===== Syntactical Elements ===== */
|
|
|
|
start
|
|
= stylesheet:stylesheet comment* { return stylesheet; }
|
|
|
|
stylesheet
|
|
= charset:(CHARSET_SYM STRING ";")? (S / CDO / CDC)*
|
|
imports:(import (CDO S* / CDC S*)*)*
|
|
rules:((ruleset / media / page) (CDO S* / CDC S*)*)* {
|
|
var importsConverted = [];
|
|
for (var i = 0; i < imports.length; i++) {
|
|
importsConverted.push(imports[i][0]);
|
|
}
|
|
|
|
var rulesConverted = [];
|
|
for (i = 0; i < rules.length; i++) {
|
|
rulesConverted.push(rules[i][0]);
|
|
}
|
|
|
|
return {
|
|
type: "stylesheet",
|
|
charset: charset !== "" ? charset[1] : null,
|
|
imports: importsConverted,
|
|
rules: rulesConverted
|
|
};
|
|
}
|
|
|
|
import
|
|
= IMPORT_SYM S* href:(STRING / URI) S* media:media_list? ";" S* {
|
|
return {
|
|
type: "import_rule",
|
|
href: href,
|
|
media: media !== "" ? media : []
|
|
};
|
|
}
|
|
|
|
media
|
|
= MEDIA_SYM S* media:media_list "{" S* rules:ruleset* "}" S* {
|
|
return {
|
|
type: "media_rule",
|
|
media: media,
|
|
rules: rules
|
|
};
|
|
}
|
|
|
|
media_list
|
|
= head:medium tail:("," S* medium)* {
|
|
var result = [head];
|
|
for (var i = 0; i < tail.length; i++) {
|
|
result.push(tail[i][2]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
medium
|
|
= ident:IDENT S* { return ident; }
|
|
|
|
page
|
|
= PAGE_SYM S* qualifier:pseudo_page?
|
|
"{" S*
|
|
declarationsHead:declaration?
|
|
declarationsTail:(";" S* declaration?)*
|
|
"}" S* {
|
|
var declarations = declarationsHead !== "" ? [declarationsHead] : [];
|
|
for (var i = 0; i < declarationsTail.length; i++) {
|
|
if (declarationsTail[i][2] !== "") {
|
|
declarations.push(declarationsTail[i][2]);
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: "page_rule",
|
|
qualifier: qualifier !== "" ? qualifier : null,
|
|
declarations: declarations
|
|
};
|
|
}
|
|
|
|
pseudo_page
|
|
= ":" ident:IDENT S* { return ident; }
|
|
|
|
operator
|
|
= "/" S* { return "/"; }
|
|
/ "," S* { return ","; }
|
|
|
|
combinator
|
|
= "+" S* { return "+"; }
|
|
/ ">" S* { return ">"; }
|
|
|
|
unary_operator
|
|
= "+"
|
|
/ "-"
|
|
|
|
property
|
|
= ident:IDENT S* { return ident; }
|
|
|
|
ruleset
|
|
= selectorsHead:selector
|
|
selectorsTail:("," S* selector)*
|
|
"{" S*
|
|
declarationsHead:declaration?
|
|
declarationsTail:(";" S* declaration?)*
|
|
"}" S* {
|
|
var selectors = [selectorsHead];
|
|
for (var i = 0; i < selectorsTail.length; i++) {
|
|
selectors.push(selectorsTail[i][2]);
|
|
}
|
|
|
|
var declarations = declarationsHead !== "" ? [declarationsHead] : [];
|
|
for (i = 0; i < declarationsTail.length; i++) {
|
|
if (declarationsTail[i][2] !== "") {
|
|
declarations.push(declarationsTail[i][2]);
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: "ruleset",
|
|
selectors: selectors,
|
|
declarations: declarations
|
|
};
|
|
}
|
|
|
|
selector
|
|
= left:simple_selector S* combinator:combinator right:selector {
|
|
return {
|
|
type: "selector",
|
|
combinator: combinator,
|
|
left: left,
|
|
right: right
|
|
};
|
|
}
|
|
/ left:simple_selector S* right:selector {
|
|
return {
|
|
type: "selector",
|
|
combinator: " ",
|
|
left: left,
|
|
right: right
|
|
};
|
|
}
|
|
/ selector:simple_selector S* { return selector; }
|
|
|
|
simple_selector
|
|
= element:element_name
|
|
qualifiers:(
|
|
id:HASH { return { type: "ID selector", id: id.substr(1) }; }
|
|
/ class
|
|
/ attrib
|
|
/ pseudo
|
|
)* {
|
|
return {
|
|
type: "simple_selector",
|
|
element: element,
|
|
qualifiers: qualifiers
|
|
};
|
|
}
|
|
/ qualifiers:(
|
|
id:HASH { return { type: "ID selector", id: id.substr(1) }; }
|
|
/ class
|
|
/ attrib
|
|
/ pseudo
|
|
)+ {
|
|
return {
|
|
type: "simple_selector",
|
|
element: "*",
|
|
qualifiers: qualifiers
|
|
};
|
|
}
|
|
|
|
class
|
|
= "." class:IDENT { return { type: "class_selector", "class": class }; }
|
|
|
|
element_name
|
|
= IDENT / '*'
|
|
|
|
attrib
|
|
= "[" S*
|
|
attribute:IDENT S*
|
|
operatorAndValue:(
|
|
('=' / INCLUDES / DASHMATCH) S*
|
|
(IDENT / STRING) S*
|
|
)?
|
|
"]" {
|
|
return {
|
|
type: "attribute_selector",
|
|
attribute: attribute,
|
|
operator: operatorAndValue !== "" ? operatorAndValue[0] : null,
|
|
value: operatorAndValue !== "" ? operatorAndValue[2] : null
|
|
};
|
|
}
|
|
|
|
pseudo
|
|
= ":"
|
|
value:(
|
|
name:FUNCTION S* params:(IDENT S*)? ")" {
|
|
return {
|
|
type: "function",
|
|
name: name,
|
|
params: params !== "" ? [params[0]] : []
|
|
};
|
|
}
|
|
/ IDENT
|
|
) {
|
|
/*
|
|
* The returned object has somewhat vague property names and values because
|
|
* the rule matches both pseudo-classes and pseudo-elements (they look the
|
|
* same at the syntactic level).
|
|
*/
|
|
return {
|
|
type: "pseudo_selector",
|
|
value: value
|
|
};
|
|
}
|
|
|
|
declaration
|
|
= property:property ":" S* expression:expr important:prio? {
|
|
return {
|
|
type: "declaration",
|
|
property: property,
|
|
expression: expression,
|
|
important: important !== "" ? true : false
|
|
};
|
|
}
|
|
|
|
prio
|
|
= IMPORTANT_SYM S*
|
|
|
|
expr
|
|
= head:term tail:(operator? term)* {
|
|
var result = head;
|
|
for (var i = 0; i < tail.length; i++) {
|
|
result = {
|
|
type: "expression",
|
|
operator: tail[i][0],
|
|
left: result,
|
|
right: tail[i][1]
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
term
|
|
= operator:unary_operator?
|
|
value:(
|
|
EMS S*
|
|
/ EXS S*
|
|
/ LENGTH S*
|
|
/ ANGLE S*
|
|
/ TIME S*
|
|
/ FREQ S*
|
|
/ PERCENTAGE S*
|
|
/ NUMBER S*
|
|
) { return { type: "value", value: operator + value[0] }; }
|
|
/ value:URI S* { return { type: "uri", value: value }; }
|
|
/ function
|
|
/ hexcolor
|
|
/ value:STRING S* { return { type: "string", value: value }; }
|
|
/ value:IDENT S* { return { type: "ident", value: value }; }
|
|
|
|
function
|
|
= name:FUNCTION S* params:expr ")" S* {
|
|
return {
|
|
type: "function",
|
|
name: name,
|
|
params: params
|
|
};
|
|
}
|
|
|
|
hexcolor
|
|
= value:HASH S* { return { type: "hexcolor", value: value}; }
|
|
|
|
/* ===== Lexical Elements ===== */
|
|
|
|
/* Macros */
|
|
|
|
h
|
|
= [0-9a-fA-F]
|
|
|
|
nonascii
|
|
= [\x80-\xFF]
|
|
|
|
unicode
|
|
= "\\" h1:h h2:h? h3:h? h4:h? h5:h? h6:h? ("\r\n" / [ \t\r\n\f])? {
|
|
return String.fromCharCode(parseInt("0x" + h1 + h2 + h3 + h4 + h5 + h6));
|
|
}
|
|
|
|
escape
|
|
= unicode
|
|
/ "\\" char:[^\r\n\f0-9a-fA-F] { return char; }
|
|
|
|
nmstart
|
|
= [_a-zA-Z]
|
|
/ nonascii
|
|
/ escape
|
|
|
|
nmchar
|
|
= [_a-zA-Z0-9-]
|
|
/ nonascii
|
|
/ escape
|
|
|
|
integer
|
|
= digits:[0-9]+ { return parseInt(digits.join("")); }
|
|
|
|
float
|
|
= before:[0-9]* "." after:[0-9]+ {
|
|
return parseFloat(before.join("") + "." + after.join(""));
|
|
}
|
|
|
|
string1
|
|
= '"' chars:([^\n\r\f\\"] / "\\" nl:nl { return nl } / escape)* '"' {
|
|
return chars.join("");
|
|
}
|
|
|
|
string2
|
|
= "'" chars:([^\n\r\f\\'] / "\\" nl:nl { return nl } / escape)* "'" {
|
|
return chars.join("");
|
|
}
|
|
|
|
comment
|
|
= "/*" [^*]* "*"+ ([^/*] [^*]* "*"+)* "/"
|
|
|
|
ident
|
|
= dash:"-"? nmstart:nmstart nmchars:nmchar* {
|
|
return dash + nmstart + nmchars.join("");
|
|
}
|
|
|
|
name
|
|
= nmchars:nmchar+ { return nmchars.join(""); }
|
|
|
|
num
|
|
= float
|
|
/ integer
|
|
|
|
string
|
|
= string1
|
|
/ string2
|
|
|
|
url
|
|
= chars:([!#$%&*-~] / nonascii / escape)* { return chars.join(""); }
|
|
|
|
s
|
|
= [ \t\r\n\f]+
|
|
|
|
w
|
|
= s?
|
|
|
|
nl
|
|
= "\n"
|
|
/ "\r\n"
|
|
/ "\r"
|
|
/ "\f"
|
|
|
|
A
|
|
= [aA]
|
|
/ "\\" "0"? "0"? "0"? "0"? "41" ("\r\n" / [ \t\r\n\f])? { return "A"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "61" ("\r\n" / [ \t\r\n\f])? { return "a"; }
|
|
|
|
C
|
|
= [cC]
|
|
/ "\\" "0"? "0"? "0"? "0"? "43" ("\r\n" / [ \t\r\n\f])? { return "C"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "63" ("\r\n" / [ \t\r\n\f])? { return "c"; }
|
|
|
|
D
|
|
= [dD]
|
|
/ "\\" "0"? "0"? "0"? "0"? "44" ("\r\n" / [ \t\r\n\f])? { return "D"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "64" ("\r\n" / [ \t\r\n\f])? { return "d"; }
|
|
|
|
E
|
|
= [eE]
|
|
/ "\\" "0"? "0"? "0"? "0"? "45" ("\r\n" / [ \t\r\n\f])? { return "E"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "65" ("\r\n" / [ \t\r\n\f])? { return "e"; }
|
|
|
|
G
|
|
= [gG]
|
|
/ "\\" "0"? "0"? "0"? "0"? "47" ("\r\n" / [ \t\r\n\f])? { return "G"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "67" ("\r\n" / [ \t\r\n\f])? { return "g"; }
|
|
/ "\\" char:[gG] { return char; }
|
|
|
|
H
|
|
= h:[hH]
|
|
/ "\\" "0"? "0"? "0"? "0"? "48" ("\r\n" / [ \t\r\n\f])? { return "H"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "68" ("\r\n" / [ \t\r\n\f])? { return "h"; }
|
|
/ "\\" char:[hH] { return char; }
|
|
|
|
I
|
|
= i:[iI]
|
|
/ "\\" "0"? "0"? "0"? "0"? "49" ("\r\n" / [ \t\r\n\f])? { return "I"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "69" ("\r\n" / [ \t\r\n\f])? { return "i"; }
|
|
/ "\\" char:[iI] { return char; }
|
|
|
|
K
|
|
= [kK]
|
|
/ "\\" "0"? "0"? "0"? "0"? "4" [bB] ("\r\n" / [ \t\r\n\f])? { return "K"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "6" [bB] ("\r\n" / [ \t\r\n\f])? { return "k"; }
|
|
/ "\\" char:[kK] { return char; }
|
|
|
|
L
|
|
= [lL]
|
|
/ "\\" "0"? "0"? "0"? "0"? "4" [cC] ("\r\n" / [ \t\r\n\f])? { return "L"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "6" [cC] ("\r\n" / [ \t\r\n\f])? { return "l"; }
|
|
/ "\\" char:[lL] { return char; }
|
|
|
|
M
|
|
= [mM]
|
|
/ "\\" "0"? "0"? "0"? "0"? "4" [dD] ("\r\n" / [ \t\r\n\f])? { return "M"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "6" [dD] ("\r\n" / [ \t\r\n\f])? { return "m"; }
|
|
/ "\\" char:[mM] { return char; }
|
|
|
|
N
|
|
= [nN]
|
|
/ "\\" "0"? "0"? "0"? "0"? "4" [eE] ("\r\n" / [ \t\r\n\f])? { return "N"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "6" [eE] ("\r\n" / [ \t\r\n\f])? { return "n"; }
|
|
/ "\\" char:[nN] { return char; }
|
|
|
|
O
|
|
= [oO]
|
|
/ "\\" "0"? "0"? "0"? "0"? "4" [fF] ("\r\n" / [ \t\r\n\f])? { return "O"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "6" [fF] ("\r\n" / [ \t\r\n\f])? { return "o"; }
|
|
/ "\\" char:[oO] { return char; }
|
|
|
|
P
|
|
= [pP]
|
|
/ "\\" "0"? "0"? "0"? "0"? "50" ("\r\n" / [ \t\r\n\f])? { return "P"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "70" ("\r\n" / [ \t\r\n\f])? { return "p"; }
|
|
/ "\\" char:[pP] { return char; }
|
|
|
|
R
|
|
= [rR]
|
|
/ "\\" "0"? "0"? "0"? "0"? "52" ("\r\n" / [ \t\r\n\f])? { return "R"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "72" ("\r\n" / [ \t\r\n\f])? { return "r"; }
|
|
/ "\\" char:[rR] { return char; }
|
|
|
|
S_
|
|
= [sS]
|
|
/ "\\" "0"? "0"? "0"? "0"? "53" ("\r\n" / [ \t\r\n\f])? { return "S"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "73" ("\r\n" / [ \t\r\n\f])? { return "s"; }
|
|
/ "\\" char:[sS] { return char; }
|
|
|
|
T
|
|
= [tT]
|
|
/ "\\" "0"? "0"? "0"? "0"? "54" ("\r\n" / [ \t\r\n\f])? { return "T"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "74" ("\r\n" / [ \t\r\n\f])? { return "t"; }
|
|
/ "\\" char:[tT] { return char; }
|
|
|
|
U
|
|
= [uU]
|
|
/ "\\" "0"? "0"? "0"? "0"? "55" ("\r\n" / [ \t\r\n\f])? { return "U"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "75" ("\r\n" / [ \t\r\n\f])? { return "u"; }
|
|
/ "\\" char:[uU] { return char; }
|
|
|
|
X
|
|
= [xX]
|
|
/ "\\" "0"? "0"? "0"? "0"? "58" ("\r\n" / [ \t\r\n\f])? { return "X"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "78" ("\r\n" / [ \t\r\n\f])? { return "x"; }
|
|
/ "\\" char:[xX] { return char; }
|
|
|
|
Z
|
|
= [zZ]
|
|
/ "\\" "0"? "0"? "0"? "0"? "5" [aA] ("\r\n" / [ \t\r\n\f])? { return "Z"; }
|
|
/ "\\" "0"? "0"? "0"? "0"? "7" [aA] ("\r\n" / [ \t\r\n\f])? { return "z"; }
|
|
/ "\\" char:[zZ] { return char; }
|
|
|
|
/* Tokens */
|
|
|
|
S "whitespace"
|
|
= comment* s
|
|
|
|
CDO "<!--"
|
|
= comment* "<!--"
|
|
|
|
CDC "-->"
|
|
= comment* "-->"
|
|
|
|
INCLUDES "~="
|
|
= comment* "~="
|
|
|
|
DASHMATCH "|="
|
|
= comment* "|="
|
|
|
|
STRING "string"
|
|
= comment* string:string { return string; }
|
|
|
|
IDENT "identifier"
|
|
= comment* ident:ident { return ident; }
|
|
|
|
HASH "hash"
|
|
= comment* "#" name:name { return "#" + name; }
|
|
|
|
IMPORT_SYM "@import"
|
|
= comment* "@" I M P O R T
|
|
|
|
PAGE_SYM "@page"
|
|
= comment* "@" P A G E
|
|
|
|
MEDIA_SYM "@media"
|
|
= comment* "@" M E D I A
|
|
|
|
CHARSET_SYM "@charset"
|
|
= comment* "@charset "
|
|
|
|
/* Note: We replace "w" with "s" here to avoid infinite recursion. */
|
|
IMPORTANT_SYM "!important"
|
|
= comment* "!" (s / comment)* I M P O R T A N T { return "!important"; }
|
|
|
|
EMS "length"
|
|
= comment* num:num e:E m:M { return num + e + m; }
|
|
|
|
EXS "length"
|
|
= comment* num:num e:E x:X { return num + e + x; }
|
|
|
|
LENGTH "length"
|
|
= comment* num:num unit:(P X / C M / M M / I N / P T / P C) {
|
|
return num + unit.join("");
|
|
}
|
|
|
|
ANGLE "angle"
|
|
= comment* num:num unit:(D E G / R A D / G R A D) {
|
|
return num + unit.join("");
|
|
}
|
|
|
|
TIME "time"
|
|
= comment* num:num unit:(m:M s:S_ { return m + s; } / S_) {
|
|
return num + unit;
|
|
}
|
|
|
|
FREQ "frequency"
|
|
= comment* num:num unit:(H Z / K H Z) { return num + unit.join(""); }
|
|
|
|
DIMENSION "dimension"
|
|
= comment* num:num unit:ident { return num + unit; }
|
|
|
|
PERCENTAGE "percentage"
|
|
= comment* num:num "%" { return num + "%"; }
|
|
|
|
NUMBER "number"
|
|
= comment* num:num { return num; }
|
|
|
|
URI "uri"
|
|
= comment* U R L "(" w value:(string / url) w ")" { return value; }
|
|
|
|
FUNCTION "function"
|
|
= comment* name:ident "(" { return name; }
|