You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

418 lines
11 KiB

  1. // CSS Grammar
  2. // ===========
  3. //
  4. // Based on grammar from CSS 2.1 specification [1] (including the errata [2]).
  5. // Generated parser builds a syntax tree composed of nested JavaScript objects,
  6. // vaguely inspired by CSS DOM [3]. The CSS DOM itself wasn't used as it is not
  7. // expressive enough (e.g. selectors are reflected as text, not structured
  8. // objects) and somewhat cumbersome.
  9. //
  10. // Limitations:
  11. //
  12. // * Many errors which should be recovered from according to the specification
  13. // (e.g. malformed declarations or unexpected end of stylesheet) are fatal.
  14. // This is a result of straightforward rewrite of the CSS grammar to PEG.js.
  15. //
  16. // [1] http://www.w3.org/TR/2011/REC-CSS2-20110607
  17. // [2] http://www.w3.org/Style/css2-updates/REC-CSS2-20110607-errata.html
  18. // [3] http://www.w3.org/TR/DOM-Level-2-Style/css.html
  19. {
  20. function extractOptional(optional, index) {
  21. return optional ? optional[index] : null;
  22. }
  23. function extractList(list, index) {
  24. return list.map(function(element) { return element[index]; });
  25. }
  26. function buildList(head, tail, index) {
  27. return [head].concat(extractList(tail, index))
  28. .filter(function(element) { return element !== null; });
  29. }
  30. function buildExpression(head, tail) {
  31. return tail.reduce(function(result, element) {
  32. return {
  33. type: "Expression",
  34. operator: element[0],
  35. left: result,
  36. right: element[1]
  37. };
  38. }, head);
  39. }
  40. }
  41. start
  42. = stylesheet:stylesheet comment* { return stylesheet; }
  43. // ----- G.1 Grammar -----
  44. stylesheet
  45. = charset:(CHARSET_SYM STRING ";")? (S / CDO / CDC)*
  46. imports:(import (CDO S* / CDC S*)*)*
  47. rules:((ruleset / media / page) (CDO S* / CDC S*)*)*
  48. {
  49. return {
  50. type: "StyleSheet",
  51. charset: extractOptional(charset, 1),
  52. imports: extractList(imports, 0),
  53. rules: extractList(rules, 0)
  54. };
  55. }
  56. import
  57. = IMPORT_SYM S* href:(STRING / URI) S* media:media_list? ";" S* {
  58. return {
  59. type: "ImportRule",
  60. href: href,
  61. media: media !== null ? media : []
  62. };
  63. }
  64. media
  65. = MEDIA_SYM S* media:media_list "{" S* rules:ruleset* "}" S* {
  66. return {
  67. type: "MediaRule",
  68. media: media,
  69. rules: rules
  70. };
  71. }
  72. media_list
  73. = head:medium tail:("," S* medium)* { return buildList(head, tail, 2); }
  74. medium
  75. = name:IDENT S* { return name; }
  76. page
  77. = PAGE_SYM S* selector:pseudo_page?
  78. "{" S*
  79. declarationsHead:declaration?
  80. declarationsTail:(";" S* declaration?)*
  81. "}" S*
  82. {
  83. return {
  84. type: "PageRule",
  85. selector: selector,
  86. declarations: buildList(declarationsHead, declarationsTail, 2)
  87. };
  88. }
  89. pseudo_page
  90. = ":" value:IDENT S* { return { type: "PseudoSelector", value: value }; }
  91. operator
  92. = "/" S* { return "/"; }
  93. / "," S* { return ","; }
  94. combinator
  95. = "+" S* { return "+"; }
  96. / ">" S* { return ">"; }
  97. property
  98. = name:IDENT S* { return name; }
  99. ruleset
  100. = selectorsHead:selector
  101. selectorsTail:("," S* selector)*
  102. "{" S*
  103. declarationsHead:declaration?
  104. declarationsTail:(";" S* declaration?)*
  105. "}" S*
  106. {
  107. return {
  108. type: "RuleSet",
  109. selectors: buildList(selectorsHead, selectorsTail, 2),
  110. declarations: buildList(declarationsHead, declarationsTail, 2)
  111. };
  112. }
  113. selector
  114. = left:simple_selector S* combinator:combinator right:selector {
  115. return {
  116. type: "Selector",
  117. combinator: combinator,
  118. left: left,
  119. right: right
  120. };
  121. }
  122. / left:simple_selector S+ right:selector {
  123. return {
  124. type: "Selector",
  125. combinator: " ",
  126. left: left,
  127. right: right
  128. };
  129. }
  130. / selector:simple_selector S* { return selector; }
  131. simple_selector
  132. = element:element_name qualifiers:(id / class / attrib / pseudo)* {
  133. return {
  134. type: "SimpleSelector",
  135. element: element,
  136. qualifiers: qualifiers
  137. };
  138. }
  139. / qualifiers:(id / class / attrib / pseudo)+ {
  140. return {
  141. type: "SimpleSelector",
  142. element: "*",
  143. qualifiers: qualifiers
  144. };
  145. }
  146. id
  147. = id:HASH { return { type: "IDSelector", id: id }; }
  148. class
  149. = "." class_:IDENT { return { type: "ClassSelector", "class": class_ }; }
  150. element_name
  151. = IDENT
  152. / "*"
  153. attrib
  154. = "[" S*
  155. attribute:IDENT S*
  156. operatorAndValue:(("=" / INCLUDES / DASHMATCH) S* (IDENT / STRING) S*)?
  157. "]"
  158. {
  159. return {
  160. type: "AttributeSelector",
  161. attribute: attribute,
  162. operator: extractOptional(operatorAndValue, 0),
  163. value: extractOptional(operatorAndValue, 2)
  164. };
  165. }
  166. pseudo
  167. = ":"
  168. value:(
  169. name:FUNCTION S* params:(IDENT S*)? ")" {
  170. return {
  171. type: "Function",
  172. name: name,
  173. params: params !== null ? [params[0]] : []
  174. };
  175. }
  176. / IDENT
  177. )
  178. { return { type: "PseudoSelector", value: value }; }
  179. declaration
  180. = name:property ':' S* value:expr prio:prio? {
  181. return {
  182. type: "Declaration",
  183. name: name,
  184. value: value,
  185. important: prio !== null
  186. };
  187. }
  188. prio
  189. = IMPORTANT_SYM S*
  190. expr
  191. = head:term tail:(operator? term)* { return buildExpression(head, tail); }
  192. term
  193. = quantity:(PERCENTAGE / LENGTH / EMS / EXS / ANGLE / TIME / FREQ / NUMBER)
  194. S*
  195. {
  196. return {
  197. type: "Quantity",
  198. value: quantity.value,
  199. unit: quantity.unit
  200. };
  201. }
  202. / value:STRING S* { return { type: "String", value: value }; }
  203. / value:URI S* { return { type: "URI", value: value }; }
  204. / function
  205. / hexcolor
  206. / value:IDENT S* { return { type: "Ident", value: value }; }
  207. function
  208. = name:FUNCTION S* params:expr ")" S* {
  209. return { type: "Function", name: name, params: params };
  210. }
  211. hexcolor
  212. = value:HASH S* { return { type: "Hexcolor", value: value }; }
  213. // ----- G.2 Lexical scanner -----
  214. // Macros
  215. h
  216. = [0-9a-f]i
  217. nonascii
  218. = [\x80-\uFFFF]
  219. unicode
  220. = "\\" digits:$(h h? h? h? h? h?) ("\r\n" / [ \t\r\n\f])? {
  221. return String.fromCharCode(parseInt(digits, 16));
  222. }
  223. escape
  224. = unicode
  225. / "\\" ch:[^\r\n\f0-9a-f]i { return ch; }
  226. nmstart
  227. = [_a-z]i
  228. / nonascii
  229. / escape
  230. nmchar
  231. = [_a-z0-9-]i
  232. / nonascii
  233. / escape
  234. string1
  235. = '"' chars:([^\n\r\f\\"] / "\\" nl:nl { return ""; } / escape)* '"' {
  236. return chars.join("");
  237. }
  238. string2
  239. = "'" chars:([^\n\r\f\\'] / "\\" nl:nl { return ""; } / escape)* "'" {
  240. return chars.join("");
  241. }
  242. comment
  243. = "/*" [^*]* "*"+ ([^/*] [^*]* "*"+)* "/"
  244. ident
  245. = prefix:$"-"? start:nmstart chars:nmchar* {
  246. return prefix + start + chars.join("");
  247. }
  248. name
  249. = chars:nmchar+ { return chars.join(""); }
  250. num
  251. = [+-]? ([0-9]* "." [0-9]+ / [0-9]+) ("e" [+-]? [0-9]+)? {
  252. return parseFloat(text());
  253. }
  254. string
  255. = string1
  256. / string2
  257. url
  258. = chars:([!#$%&*-\[\]-~] / nonascii / escape)* { return chars.join(""); }
  259. s
  260. = [ \t\r\n\f]+
  261. w
  262. = s?
  263. nl
  264. = "\n"
  265. / "\r\n"
  266. / "\r"
  267. / "\f"
  268. A = "a"i / "\\" "0"? "0"? "0"? "0"? [\x41\x61] ("\r\n" / [ \t\r\n\f])? { return "a"; }
  269. C = "c"i / "\\" "0"? "0"? "0"? "0"? [\x43\x63] ("\r\n" / [ \t\r\n\f])? { return "c"; }
  270. D = "d"i / "\\" "0"? "0"? "0"? "0"? [\x44\x64] ("\r\n" / [ \t\r\n\f])? { return "d"; }
  271. E = "e"i / "\\" "0"? "0"? "0"? "0"? [\x45\x65] ("\r\n" / [ \t\r\n\f])? { return "e"; }
  272. G = "g"i / "\\" "0"? "0"? "0"? "0"? [\x47\x67] ("\r\n" / [ \t\r\n\f])? / "\\g"i { return "g"; }
  273. H = "h"i / "\\" "0"? "0"? "0"? "0"? [\x48\x68] ("\r\n" / [ \t\r\n\f])? / "\\h"i { return "h"; }
  274. I = "i"i / "\\" "0"? "0"? "0"? "0"? [\x49\x69] ("\r\n" / [ \t\r\n\f])? / "\\i"i { return "i"; }
  275. K = "k"i / "\\" "0"? "0"? "0"? "0"? [\x4b\x6b] ("\r\n" / [ \t\r\n\f])? / "\\k"i { return "k"; }
  276. L = "l"i / "\\" "0"? "0"? "0"? "0"? [\x4c\x6c] ("\r\n" / [ \t\r\n\f])? / "\\l"i { return "l"; }
  277. M = "m"i / "\\" "0"? "0"? "0"? "0"? [\x4d\x6d] ("\r\n" / [ \t\r\n\f])? / "\\m"i { return "m"; }
  278. N = "n"i / "\\" "0"? "0"? "0"? "0"? [\x4e\x6e] ("\r\n" / [ \t\r\n\f])? / "\\n"i { return "n"; }
  279. O = "o"i / "\\" "0"? "0"? "0"? "0"? [\x4f\x6f] ("\r\n" / [ \t\r\n\f])? / "\\o"i { return "o"; }
  280. P = "p"i / "\\" "0"? "0"? "0"? "0"? [\x50\x70] ("\r\n" / [ \t\r\n\f])? / "\\p"i { return "p"; }
  281. R = "r"i / "\\" "0"? "0"? "0"? "0"? [\x52\x72] ("\r\n" / [ \t\r\n\f])? / "\\r"i { return "r"; }
  282. S_ = "s"i / "\\" "0"? "0"? "0"? "0"? [\x53\x73] ("\r\n" / [ \t\r\n\f])? / "\\s"i { return "s"; }
  283. T = "t"i / "\\" "0"? "0"? "0"? "0"? [\x54\x74] ("\r\n" / [ \t\r\n\f])? / "\\t"i { return "t"; }
  284. U = "u"i / "\\" "0"? "0"? "0"? "0"? [\x55\x75] ("\r\n" / [ \t\r\n\f])? / "\\u"i { return "u"; }
  285. X = "x"i / "\\" "0"? "0"? "0"? "0"? [\x58\x78] ("\r\n" / [ \t\r\n\f])? / "\\x"i { return "x"; }
  286. Z = "z"i / "\\" "0"? "0"? "0"? "0"? [\x5a\x7a] ("\r\n" / [ \t\r\n\f])? / "\\z"i { return "z"; }
  287. // Tokens
  288. S "whitespace"
  289. = comment* s
  290. CDO "<!--"
  291. = comment* "<!--"
  292. CDC "-->"
  293. = comment* "-->"
  294. INCLUDES "~="
  295. = comment* "~="
  296. DASHMATCH "|="
  297. = comment* "|="
  298. STRING "string"
  299. = comment* string:string { return string; }
  300. IDENT "identifier"
  301. = comment* ident:ident { return ident; }
  302. HASH "hash"
  303. = comment* "#" name:name { return "#" + name; }
  304. IMPORT_SYM "@import"
  305. = comment* "@" I M P O R T
  306. PAGE_SYM "@page"
  307. = comment* "@" P A G E
  308. MEDIA_SYM "@media"
  309. = comment* "@" M E D I A
  310. CHARSET_SYM "@charset"
  311. = comment* "@charset "
  312. // We use |s| instead of |w| here to avoid infinite recursion.
  313. IMPORTANT_SYM "!important"
  314. = comment* "!" (s / comment)* I M P O R T A N T
  315. EMS "length"
  316. = comment* value:num E M { return { value: value, unit: "em" }; }
  317. EXS "length"
  318. = comment* value:num E X { return { value: value, unit: "ex" }; }
  319. LENGTH "length"
  320. = comment* value:num P X { return { value: value, unit: "px" }; }
  321. / comment* value:num C M { return { value: value, unit: "cm" }; }
  322. / comment* value:num M M { return { value: value, unit: "mm" }; }
  323. / comment* value:num I N { return { value: value, unit: "in" }; }
  324. / comment* value:num P T { return { value: value, unit: "pt" }; }
  325. / comment* value:num P C { return { value: value, unit: "pc" }; }
  326. ANGLE "angle"
  327. = comment* value:num D E G { return { value: value, unit: "deg" }; }
  328. / comment* value:num R A D { return { value: value, unit: "rad" }; }
  329. / comment* value:num G R A D { return { value: value, unit: "grad" }; }
  330. TIME "time"
  331. = comment* value:num M S_ { return { value: value, unit: "ms" }; }
  332. / comment* value:num S_ { return { value: value, unit: "s" }; }
  333. FREQ "frequency"
  334. = comment* value:num H Z { return { value: value, unit: "hz" }; }
  335. / comment* value:num K H Z { return { value: value, unit: "kh" }; }
  336. PERCENTAGE "percentage"
  337. = comment* value:num "%" { return { value: value, unit: "%" }; }
  338. NUMBER "number"
  339. = comment* value:num { return { value: value, unit: null }; }
  340. URI "uri"
  341. = comment* U R L "("i w url:string w ")" { return url; }
  342. / comment* U R L "("i w url:url w ")" { return url; }
  343. FUNCTION "function"
  344. = comment* name:ident "(" { return name; }