parser.mly 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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. (* TODO: move this to utils *)
  13. let ( |> ) a b = b a
  14. let rec filter_none = function
  15. | [] -> []
  16. | None :: tl -> filter_none tl
  17. | Some hd :: tl -> hd :: filter_none tl
  18. type term_t = Term of expr | Operator of string
  19. let rec transform_value f = function
  20. | Concat terms -> Concat (List.map (transform_value f) terms)
  21. | Function (name, arg) -> Function (name, transform_value f arg)
  22. | Unary (op, term) -> Unary (op, transform_value f term)
  23. | Nary (op, terms) -> Nary (op, List.map (transform_value f) terms)
  24. | value -> f value
  25. let concat_terms terms =
  26. let rec transform_ops = function
  27. | [] -> []
  28. | Term left :: Operator op :: Term right :: tl ->
  29. transform_ops (Term (Nary (op, [left; right])) :: tl)
  30. | Term hd :: tl -> hd :: transform_ops tl
  31. | Operator op :: _ -> raise (SyntaxError ("unexpected operator \"" ^ op ^ "\""))
  32. in
  33. let rec flatten_nary = function
  34. | [] -> []
  35. | Nary (op, Nary (op2, left) :: right) :: tl when op2 = op ->
  36. Nary (op, flatten_nary left @ flatten_nary right) :: flatten_nary tl
  37. | hd :: tl -> hd :: flatten_nary tl
  38. in
  39. match terms |> transform_ops |> flatten_nary with
  40. | [hd] -> hd
  41. | l -> Concat l
  42. %}
  43. (* Tokens *)
  44. %token S CDO CDC IMPORT_SYM PAGE_SYM MEDIA_SYM CHARSET_SYM FONT_FACE_SYM
  45. %token NAMESPACE_SYM KEYFRAMES_SYM SUPPORTS_SYM IMPORTANT_SYM
  46. %token <float> PERCENTAGE NUMBER
  47. %token <float * string> UNIT_VALUE
  48. %token <string> COMBINATOR RELATION STRING IDENT HASH URI FUNCTION
  49. %token LPAREN RPAREN LBRACE RBRACE LBRACK RBRACK SEMICOL COLON COMMA DOT PLUS
  50. %token MINUS SLASH STAR ONLY AND OR NOT FROM TO EOF
  51. (* Start symbol *)
  52. %type <Types.stylesheet> stylesheet
  53. %start stylesheet
  54. %%
  55. (* list with arbitrary whitespace between elements and separators *)
  56. %inline wslist(sep, x): S* l=separated_list(sep, terminated(x, S*)) { l }
  57. %inline wspreceded(prefix, x): p=preceded(pair(prefix, S*), x) { p }
  58. cd: CDO S* | CDC S* {}
  59. stylesheet:
  60. | charset = charset? S* cd*
  61. imports = terminated(import, cd*)*
  62. namespaces = terminated(namespace, cd*)*
  63. statements = terminated(nested_statement, cd*)*
  64. EOF
  65. { let charset = match charset with None -> [] | Some c -> [c] in
  66. charset @ imports @ namespaces @ statements }
  67. nested_statement:
  68. | s=ruleset | s=media | s=page | s=font_face_rule | s=keyframes_rule
  69. | s=supports_rule
  70. { s }
  71. group_rule_body:
  72. | LBRACE S* statements=nested_statement* RBRACE S*
  73. { statements }
  74. charset:
  75. | CHARSET_SYM name=STRING S* SEMICOL
  76. { Charset name }
  77. import:
  78. | IMPORT_SYM S* tgt=string_or_uri media=media_query_list SEMICOL S*
  79. { Import (tgt, media) }
  80. %inline string_or_uri:
  81. | str=STRING { Strlit str }
  82. | uri=URI { Uri uri }
  83. namespace:
  84. | NAMESPACE_SYM S* prefix=terminated(namespace_prefix, S*)? ns=string_or_uri S* SEMICOL S*
  85. { Namespace (prefix, ns) }
  86. %inline namespace_prefix:
  87. | prefix=IDENT
  88. { prefix }
  89. media:
  90. | MEDIA_SYM queries=media_query_list rulesets=group_rule_body
  91. { Media (queries, rulesets) }
  92. media_query_list:
  93. | S*
  94. { [] }
  95. | S* hd=media_query tl=wspreceded(COMMA, media_query)*
  96. { hd :: tl }
  97. media_query:
  98. | prefix=only_or_not? typ=media_type S* feat=wspreceded(AND, media_expr)*
  99. { (prefix, Some typ, feat) }
  100. | hd=media_expr tl=wspreceded(AND, media_expr)*
  101. { (None, None, (hd :: tl)) }
  102. %inline only_or_not:
  103. | ONLY S* { "only" }
  104. | NOT S* { "not" }
  105. %inline media_type:
  106. | id=IDENT { id }
  107. media_expr:
  108. | LPAREN S* feature=media_feature S* value=wspreceded(COLON, expr)? RPAREN S*
  109. { (feature, value) }
  110. %inline media_feature:
  111. | id=IDENT { id }
  112. page:
  113. | PAGE_SYM S* pseudo=pseudo_page? decls=decls_block
  114. { Page (pseudo, decls) }
  115. pseudo_page:
  116. | COLON pseudo=IDENT S*
  117. { pseudo }
  118. font_face_rule:
  119. | FONT_FACE_SYM S* LBRACE S* hd=descriptor_declaration?
  120. tl=wspreceded(SEMICOL, descriptor_declaration?)* RBRACE S*
  121. { Font_face (filter_none (hd :: tl)) }
  122. descriptor_declaration:
  123. | name=property COLON S* value=expr
  124. { (name, value) }
  125. keyframes_rule:
  126. | KEYFRAMES_SYM S* id=IDENT S* LBRACE S* rules=keyframe_ruleset* RBRACE S*
  127. { Keyframes (id, rules) }
  128. keyframe_ruleset:
  129. | selector=keyframe_selector S* decls=decls_block
  130. { (selector, decls) }
  131. keyframe_selector:
  132. | FROM { Ident "from" }
  133. | TO { Ident "to" }
  134. | n=PERCENTAGE { Number (n, Some "%") }
  135. supports_rule:
  136. | SUPPORTS_SYM S* cond=supports_condition S* body=group_rule_body
  137. { Supports (cond, body) }
  138. supports_condition:
  139. | c=supports_negation
  140. | c=supports_conjunction
  141. | c=supports_disjunction
  142. | c=supports_condition_in_parens
  143. { c }
  144. supports_condition_in_parens:
  145. | LPAREN S* c=supports_condition S* RPAREN
  146. | c=supports_declaration_condition
  147. (*XXX: | c=general_enclosed*)
  148. { c }
  149. supports_negation:
  150. | NOT S+ c=supports_condition_in_parens
  151. { Not c }
  152. supports_conjunction:
  153. | hd=supports_condition_in_parens tl=preceded(delimited(S+, AND, S+), supports_condition_in_parens)+
  154. { And (hd :: tl) }
  155. supports_disjunction:
  156. | hd=supports_condition_in_parens tl=preceded(delimited(S+, OR, S+), supports_condition_in_parens)+
  157. { Or (hd :: tl) }
  158. supports_declaration_condition:
  159. | LPAREN S* decl=declaration RPAREN
  160. { Decl decl }
  161. (*XXX:
  162. general_enclosed:
  163. | ( FUNCTION | LPAREN ) ( any | unused )* RPAREN
  164. { }
  165. any:
  166. [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
  167. | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
  168. | DASHMATCH | ':' | FUNCTION S* [any|unused]* ')'
  169. | '(' S* [any|unused]* ')' | '[' S* [any|unused]* ']'
  170. ]
  171. S*;
  172. unused : block | ATKEYWORD S* | ';' S* | CDO S* | CDC S*;
  173. *)
  174. %inline decls_block:
  175. | LBRACE S* hd=declaration? tl=wspreceded(SEMICOL, declaration?)* RBRACE S*
  176. { filter_none (hd :: tl) }
  177. ruleset:
  178. | selectors_hd = selector
  179. selectors_tl = wspreceded(COMMA, selector)*
  180. decls = decls_block
  181. { Ruleset (selectors_hd :: selectors_tl, decls) }
  182. selector:
  183. | simple=simple_selector S*
  184. { Simple simple }
  185. | left=simple_selector S+ right=selector
  186. { Combinator (Simple left, " ", right) }
  187. | left=simple_selector S* com=combinator right=selector
  188. { Combinator (Simple left, com, right) }
  189. %inline combinator:
  190. | PLUS S* { "+" }
  191. | c=COMBINATOR S* { c }
  192. simple_selector:
  193. | elem=element_name addons=element_addon*
  194. { elem ^ String.concat "" addons }
  195. | addons=element_addon+
  196. { String.concat "" addons }
  197. %inline element_addon:
  198. | a=HASH | a=cls | a=attrib | a=pseudo
  199. { a }
  200. element_name:
  201. | tag=IDENT { tag }
  202. | STAR { "*" }
  203. cls:
  204. | DOT name=IDENT
  205. { "." ^ name }
  206. attrib:
  207. | LBRACK S* left=IDENT S* right=pair(RELATION, rel_value)? RBRACK
  208. { let right = match right with None -> "" | Some (op, term) -> op ^ term in
  209. "[" ^ left ^ right ^ "]" }
  210. %inline rel_value:
  211. | S* id=IDENT S* { id }
  212. | S* s=STRING S* { "\"" ^ s ^ "\"" }
  213. pseudo:
  214. | COLON id=IDENT
  215. { ":" ^ id }
  216. | COLON f=FUNCTION S* arg=terminated(IDENT, S*)? RPAREN
  217. { let arg = match arg with None -> "" | Some id -> id in
  218. ":" ^ f ^ "(" ^ arg ^ ")" }
  219. declaration:
  220. | name=property S* COLON S* value=expr important=boption(pair(IMPORTANT_SYM, S*))
  221. { (String.lowercase name, value, important) }
  222. %inline property: name=IDENT { name }
  223. expr:
  224. | l=exprl { concat_terms l }
  225. %inline exprl:
  226. | hd=term tl=opterm* { Term hd :: List.concat tl }
  227. %inline opterm:
  228. | t=term { [Term t] }
  229. | op=operator t=term { [Operator op; Term t] }
  230. %inline operator:
  231. | SLASH S* { "/" }
  232. | COMMA S* { "," }
  233. term:
  234. | op=unary_operator v=numval S* { Unary (op, v) }
  235. | v=numval S* { v }
  236. | str=STRING S* { Strlit str }
  237. | id=IDENT S* { Ident id }
  238. | uri=URI S* { Uri uri }
  239. | fn=FUNCTION arg=expr RPAREN S* { Function (fn, arg) }
  240. | hex=HASH S*
  241. { let h = "[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]" in
  242. if Str.string_match (Str.regexp ("^" ^ h ^ "\\(" ^ h ^ "\\)?$")) hex 0
  243. then Hexcolor (String.lowercase hex)
  244. else raise (SyntaxError ("invalid color #" ^ hex)) }
  245. unary_operator:
  246. | MINUS { "-" }
  247. | PLUS { "+" }
  248. %inline numval:
  249. | n=NUMBER { Number (n, None) }
  250. | v=UNIT_VALUE { let n, u = v in Number (n, Some u) }
  251. | n=PERCENTAGE { Number (n, Some "%") }