parser.mly 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. %{
  2. (* CSS grammar based on:
  3. * - http://www.w3.org/TR/CSS2/grammar.html
  4. * - http://www.w3.org/TR/css3-mediaqueries/
  5. * - http://www.w3.org/TR/css3-fonts/
  6. * - http://www.w3.org/TR/css3-namespace/
  7. * - http://www.w3.org/TR/css3-animations/
  8. * - http://www.w3.org/TR/css3-conditional/
  9. *)
  10. open Lexing
  11. open Types
  12. open Util
  13. type term = Term of expr | Operator of string
  14. let concat_terms terms =
  15. let rec transform_ops = function
  16. | [] -> []
  17. | Term left :: Operator op :: Term right :: tl ->
  18. transform_ops (Term (Nary (op, [left; right])) :: tl)
  19. | Term hd :: tl -> hd :: transform_ops tl
  20. | Operator op :: _ -> raise (Syntax_error ("unexpected operator \"" ^ op ^ "\""))
  21. in
  22. let rec flatten_nary = function
  23. | [] -> []
  24. | Nary (op1, Nary (op2, left) :: right) :: tl when op1 = op2 ->
  25. flatten_nary (Nary (op1, flatten_nary left @ flatten_nary right) :: tl)
  26. | hd :: tl -> hd :: flatten_nary tl
  27. in
  28. match terms |> transform_ops |> flatten_nary with
  29. | [hd] -> hd
  30. | l -> Concat l
  31. (* TODO: move this to a normalization stage, because the syntax should be
  32. * preserved during parsing (e.g. for -echo command) *)
  33. let unary_number = function
  34. | Unary ("-", Number (n, u)) -> Number (-.n, u)
  35. | Unary ("+", (Number _ as n)) -> n
  36. | value -> value
  37. let rec append_addons base = function
  38. | [] ->
  39. base
  40. | `Id id :: tl ->
  41. append_addons (Id (base, id)) tl
  42. | `Class cls :: tl ->
  43. append_addons (Class (base, cls)) tl
  44. | `Attribute (attr, value) :: tl ->
  45. append_addons (Attribute (base, attr, value)) tl
  46. | `Pseudo_class (f, args) :: tl ->
  47. append_addons (Pseudo_class (base, f, args)) tl
  48. | `Pseudo_element elem :: tl ->
  49. append_addons (Pseudo_element (base, elem)) tl
  50. %}
  51. (* Tokens *)
  52. %token S CDO CDC IMPORT_SYM PAGE_SYM MEDIA_SYM CHARSET_SYM FONT_FACE_SYM
  53. %token NAMESPACE_SYM SUPPORTS_SYM IMPORTANT_SYM
  54. %token <float> PERCENTAGE NUMBER
  55. %token <float * string> UNIT_VALUE
  56. %token <string> KEYFRAMES_SYM VIEWPORT_SYM COMBINATOR RELATION STRING IDENT HASH
  57. %token <string> URI FUNCTION
  58. %token LPAREN RPAREN LBRACE RBRACE LBRACK RBRACK SEMICOL COLON DOUBLE_COLON
  59. %token COMMA DOT PLUS MINUS SLASH STAR ONLY AND (*OR*) NOT FROM TO EOF
  60. %token WS_AND WS_OR ODD EVEN
  61. %token <int * int> FORMULA
  62. (* Start symbol *)
  63. %type <Types.stylesheet> stylesheet
  64. %start stylesheet
  65. %%
  66. (* list with arbitrary whitespace between elements and separators *)
  67. %inline ig2(a, b): a b {}
  68. %inline ig3(a, b, c): a b c {}
  69. %inline wslist(sep, x): S* l=separated_list(sep, terminated(x, S*)) { l }
  70. %inline wspreceded(prefix, x): p=preceded(ig2(prefix, S*), x) { p }
  71. %inline all_and: AND | WS_AND {}
  72. cd: CDO S* | CDC S* {}
  73. stylesheet:
  74. | charset = charset? S* cd*
  75. imports = terminated(import, cd*)*
  76. namespaces = terminated(namespace, cd*)*
  77. statements = terminated(nested_statement, cd*)*
  78. EOF
  79. { let charset = match charset with None -> [] | Some c -> [c] in
  80. charset @ imports @ namespaces @ statements }
  81. nested_statement:
  82. | s=ruleset | s=media | s=page | s=font_face_rule | s=keyframes_rule
  83. | s=supports_rule | s=viewport_rule
  84. { s }
  85. group_rule_body:
  86. | LBRACE S* statements=nested_statement* RBRACE S*
  87. { statements }
  88. charset:
  89. | CHARSET_SYM name=STRING S* SEMICOL
  90. { Charset name }
  91. import:
  92. | IMPORT_SYM S* tgt=string_or_uri media=media_query_list SEMICOL S*
  93. { Import (tgt, media) }
  94. %inline string_or_uri:
  95. | str=STRING { Strlit str }
  96. | uri=URI { Uri uri }
  97. namespace:
  98. | NAMESPACE_SYM S* prefix=terminated(namespace_prefix, S*)? ns=string_or_uri S* SEMICOL S*
  99. { Namespace (prefix, ns) }
  100. %inline namespace_prefix:
  101. | prefix=IDENT
  102. { prefix }
  103. media:
  104. | MEDIA_SYM queries=media_query_list rulesets=group_rule_body
  105. { Media (queries, rulesets) }
  106. media_query_list:
  107. | S*
  108. { [] }
  109. | S* hd=media_query tl=wspreceded(COMMA, media_query)*
  110. { hd :: tl }
  111. media_query:
  112. | prefix=only_or_not? typ=media_type S* feat=wspreceded(all_and, media_expr)*
  113. { (prefix, Some typ, feat) }
  114. | hd=media_expr tl=wspreceded(all_and, media_expr)*
  115. { (None, None, (hd :: tl)) }
  116. %inline only_or_not:
  117. | ONLY S* { "only" }
  118. | NOT S* { "not" }
  119. %inline media_type:
  120. | id=IDENT { id }
  121. media_expr:
  122. | LPAREN S* feature=media_feature S* value=wspreceded(COLON, expr)? RPAREN S*
  123. { (feature, value) }
  124. %inline media_feature:
  125. | id=IDENT { id }
  126. page:
  127. | PAGE_SYM S* pseudo=pseudo_page? decls=decls_block
  128. { Page (pseudo, decls) }
  129. pseudo_page:
  130. | COLON pseudo=IDENT S*
  131. { pseudo }
  132. font_face_rule:
  133. | FONT_FACE_SYM S* LBRACE S* hd=descriptor_declaration?
  134. tl=wspreceded(SEMICOL, descriptor_declaration?)* RBRACE S*
  135. { Font_face (filter_none (hd :: tl)) }
  136. descriptor_declaration:
  137. | name=property COLON S* value=expr
  138. { (name, value) }
  139. keyframes_rule:
  140. | pre=KEYFRAMES_SYM S* id=IDENT S* LBRACE S* rules=keyframe_ruleset* RBRACE S*
  141. { Keyframes (pre, id, rules) }
  142. keyframe_ruleset:
  143. | selector=keyframe_selector S* decls=decls_block
  144. { (selector, decls) }
  145. keyframe_selector:
  146. | FROM { Ident "from" }
  147. | TO { Ident "to" }
  148. | n=PERCENTAGE { Number (n, Some "%") }
  149. supports_rule:
  150. | SUPPORTS_SYM S* cond=supports_condition S* body=group_rule_body
  151. { Supports (cond, body) }
  152. supports_condition:
  153. | c=supports_negation
  154. | c=supports_conjunction
  155. | c=supports_disjunction
  156. | c=supports_condition_in_parens
  157. { c }
  158. supports_condition_in_parens:
  159. | LPAREN S* c=supports_condition S* RPAREN
  160. | c=supports_declaration_condition
  161. (*XXX: | c=general_enclosed*)
  162. { c }
  163. supports_negation:
  164. | NOT S+ c=supports_condition_in_parens
  165. { Not c }
  166. supports_conjunction:
  167. | hd=supports_condition_in_parens tl=preceded(WS_AND, supports_condition_in_parens)+
  168. { And (hd :: tl) }
  169. supports_disjunction:
  170. | hd=supports_condition_in_parens tl=preceded(WS_OR, supports_condition_in_parens)+
  171. { Or (hd :: tl) }
  172. supports_declaration_condition:
  173. | LPAREN S* decl=supports_declaration RPAREN
  174. { Decl decl }
  175. supports_declaration:
  176. | name=property S* COLON S* value=expr
  177. { (name, value) }
  178. (*XXX:
  179. general_enclosed:
  180. | ( FUNCTION | LPAREN ) ( any | unused )* RPAREN
  181. { Enclosed expr }
  182. any:
  183. [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
  184. | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
  185. | DASHMATCH | ':' | FUNCTION S* [any|unused]* ')'
  186. | '(' S* [any|unused]* ')' | '[' S* [any|unused]* ']'
  187. ]
  188. S*;
  189. unused : block | ATKEYWORD S* | ';' S* | CDO S* | CDC S*;
  190. *)
  191. viewport_rule:
  192. | pre=VIEWPORT_SYM S* decls=decls_block
  193. { Viewport (pre, decls) }
  194. %inline decls_block:
  195. | LBRACE S* hd=declaration? tl=wspreceded(SEMICOL, declaration?)* RBRACE S*
  196. { filter_none (hd :: tl) }
  197. ruleset:
  198. | selectors_hd = selector
  199. selectors_tl = wspreceded(COMMA, selector)*
  200. decls = decls_block
  201. { Ruleset (selectors_hd :: selectors_tl, decls) }
  202. selector:
  203. | simple=simple_selector S*
  204. { simple }
  205. | left=simple_selector S+ right=selector
  206. { Combinator (left, " ", right) }
  207. | left=simple_selector S* com=combinator right=selector
  208. { Combinator (left, com, right) }
  209. %inline combinator:
  210. | PLUS S* { "+" }
  211. | c=COMBINATOR S* { c }
  212. simple_selector:
  213. | elem=element_name addons=element_addon*
  214. { append_addons elem addons }
  215. | addons=element_addon+
  216. { append_addons No_element addons }
  217. %inline element_addon:
  218. | id=HASH { `Id id }
  219. | addon=cls | addon=attrib | addon=pseudo_class { addon }
  220. element_name:
  221. | tag=IDENT { Element (String.lowercase tag) }
  222. | STAR { All_elements }
  223. cls:
  224. | DOT name=IDENT
  225. { `Class name }
  226. attrib:
  227. | LBRACK S* left=IDENT S* RBRACK
  228. { `Attribute (String.lowercase left, None) }
  229. | LBRACK S* left=IDENT S* op=RELATION right=rel_value RBRACK
  230. { `Attribute (String.lowercase left, Some (op, right)) }
  231. %inline rel_value:
  232. | S* id=IDENT S* { Ident id }
  233. | S* s=STRING S* { Strlit s }
  234. pseudo_class:
  235. | COLON id=IDENT
  236. { `Pseudo_class (String.lowercase id, None) }
  237. | COLON f=FUNCTION args=wslist(COMMA, function_arg) RPAREN
  238. { `Pseudo_class (String.lowercase f, Some args) }
  239. | DOUBLE_COLON id=IDENT
  240. { `Pseudo_element (String.lowercase id) }
  241. function_arg:
  242. | s=simple_selector
  243. { Nested_selector s }
  244. | EVEN
  245. { Nth Even }
  246. | ODD
  247. { Nth Odd }
  248. | f=FORMULA
  249. { let a, b = f in Nth (Formula (a, b)) }
  250. | sign=sign? n=NUMBER
  251. {
  252. if is_int n then begin
  253. let b = int_of_float (match sign with Some MINUS -> -.n | _ -> n) in
  254. Nth (Formula (0, b))
  255. end else
  256. raise (Syntax_error ("unexpected float '" ^ string_of_float n ^
  257. "', expected int"))
  258. }
  259. %inline sign: PLUS { PLUS } | MINUS { MINUS }
  260. declaration:
  261. | name=property S* COLON S* value=expr important=boption(ig2(IMPORTANT_SYM, S*))
  262. { (String.lowercase name, value, important) }
  263. %inline property:
  264. | name=IDENT { name }
  265. | STAR name=IDENT { "*" ^ name } (* IE7 property name hack *)
  266. expr:
  267. | l=exprl { concat_terms l }
  268. %inline exprl:
  269. | hd=term tl=opterm* { Term hd :: List.concat tl }
  270. %inline opterm:
  271. | t=term { [Term t] }
  272. | op=operator t=term { [Operator op; Term t] }
  273. %inline operator:
  274. | SLASH S* { "/" }
  275. | COMMA S* { "," }
  276. term:
  277. | op=unary_operator v=numval S* { unary_number (Unary (op, v)) }
  278. | v=numval S* { v }
  279. | str=STRING S* { Strlit str }
  280. | id=IDENT S* { Ident (String.lowercase id) }
  281. | ONLY S* { Ident "only" }
  282. | NOT S* { Ident "not" }
  283. | AND S* { Ident "and" }
  284. | FROM S* { Ident "from" }
  285. | TO S* { Ident "to" }
  286. | uri=URI S* { Uri uri }
  287. | fn=FUNCTION arg=expr RPAREN S* { Function (String.lowercase fn, arg) }
  288. | key=IDENT S* COLON S* value=term
  289. { Key_value (key, ":", value) }
  290. | key=IDENT S* DOT S* value=term
  291. { Key_value (key, ".", value) }
  292. | key=IDENT S* rel=RELATION S* value=term
  293. {
  294. if rel = "="
  295. then Key_value (key, "=", value)
  296. else raise (Syntax_error ("unexpected '" ^ rel ^ "'"))
  297. }
  298. | hex=HASH S*
  299. {
  300. let h = "[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]" in
  301. if Str.string_match (Str.regexp ("^" ^ h ^ "\\(" ^ h ^ "\\)?$")) hex 0
  302. then Hexcolor (String.lowercase hex)
  303. else raise (Syntax_error ("invalid color #" ^ hex))
  304. }
  305. unary_operator:
  306. | MINUS { "-" }
  307. | PLUS { "+" }
  308. %inline numval:
  309. | n=NUMBER { Number (n, None) }
  310. | v=UNIT_VALUE { let n, u = v in Number (n, Some (String.lowercase u)) }
  311. | n=PERCENTAGE { Number (n, Some "%") }