pegjs/examples/css.pegjs
David Majda ee8c121676 Use labeled expressions and variables instead of $1, $2, etc.
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
2010-06-07 09:23:04 +02:00

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; }