parser.mly 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. %{
  2. (* CSS grammar based on http://www.w3.org/TR/CSS2/grammar.html *)
  3. open Lexing
  4. open Types
  5. let ( |> ) a b = b a
  6. let filter_none l =
  7. let rec filter l = function
  8. | [] -> l
  9. | None :: tl -> filter l tl
  10. | Some hd :: tl -> filter (hd :: l) tl
  11. in
  12. List.rev (filter [] l)
  13. type term_t = Term of expr | Operator of string
  14. let rec transform_value f = function
  15. | Concat terms -> Concat (List.map (transform_value f) terms)
  16. | Function (name, arg) -> Function (name, transform_value f arg)
  17. | Unary (op, term) -> Unary (op, transform_value f term)
  18. | Nary (op, terms) -> Nary (op, List.map (transform_value f) terms)
  19. | value -> f value
  20. let concat_terms terms =
  21. let rec transform_ops = function
  22. | [] -> []
  23. | Term left :: Operator op :: Term right :: tl ->
  24. Nary (op, [left; right]) :: transform_ops tl
  25. | Term hd :: tl -> hd :: transform_ops tl
  26. | Operator op :: _ -> raise (SyntaxError ("unexpected operator \"" ^ op ^ "\""))
  27. in
  28. let rec flatten_nary = function
  29. | [] -> []
  30. | Nary (op, Nary (op2, left) :: right) :: tl when op2 = op ->
  31. Nary (op, flatten_nary left @ flatten_nary right) :: flatten_nary tl
  32. | hd :: tl -> hd :: flatten_nary tl
  33. in
  34. let comma_to_concat =
  35. List.map (transform_value (function
  36. | Nary (",", terms) -> Concat terms
  37. | value -> value
  38. ))
  39. in
  40. match terms |> transform_ops |> flatten_nary |> comma_to_concat with
  41. | [hd] -> hd
  42. | l -> Concat l
  43. %}
  44. (* Tokens *)
  45. %token S CDO CDC IMPORT_SYM PAGE_SYM MEDIA_SYM CHARSET_SYM
  46. %token IMPORTANT_SYM
  47. %token <float> NUMBER
  48. %token <float * string> UNIT_VALUE
  49. %token <string> COMBINATOR RELATION STRING IDENT HASH URI FUNCTION
  50. %token RPAREN LBRACE RBRACE LBRACK RBRACK SEMICOL COLON COMMA DOT PLUS MINUS
  51. %token SLASH STAR EOF
  52. (* Start symbol *)
  53. %type <Types.stylesheet> stylesheet
  54. %start stylesheet
  55. %%
  56. (* list with arbitrary whitespace between elements and separators *)
  57. %inline wslist(sep, x): S? l=separated_list(sep, terminated(x, S?)) { l }
  58. cd: CDO S? | CDC S? {}
  59. stylesheet:
  60. | charset = charset? S? cd*
  61. imports = terminated(import, cd*)*
  62. statements = terminated(statement, cd*)*
  63. EOF
  64. { let charset = match charset with None -> [] | Some c -> [c] in
  65. charset @ imports @ statements }
  66. %inline statement:
  67. | s=ruleset | s=media | s=page
  68. { s }
  69. charset:
  70. | CHARSET_SYM name=STRING SEMICOL
  71. { Charset name }
  72. %inline string_or_uri:
  73. | s=STRING | s=URI
  74. { s }
  75. import:
  76. | IMPORT_SYM S? tgt=string_or_uri media=wslist(COMMA, IDENT) SEMICOL S?
  77. { Import (tgt, media) }
  78. media:
  79. | MEDIA_SYM queries=wslist(COMMA, IDENT) LBRACE S? rulesets=ruleset* RBRACE S?
  80. { Media (queries, rulesets) }
  81. page:
  82. | PAGE_SYM S? pseudo=pseudo_page? decls=decls_block
  83. { Page (pseudo, decls) }
  84. pseudo_page:
  85. | COLON pseudo=IDENT S?
  86. { pseudo }
  87. %inline decls_block:
  88. | LBRACE S? hd=declaration? tl=preceded(pair(SEMICOL, S?), declaration?)* RBRACE S?
  89. { filter_none (hd :: tl) }
  90. ruleset:
  91. | selectors_hd = selector
  92. selectors_tl = preceded(pair(COMMA, S?), selector)*
  93. decls = decls_block
  94. { Ruleset (selectors_hd :: selectors_tl, decls) }
  95. selector:
  96. | hd=simple_selector S?
  97. { [hd] }
  98. | hd=simple_selector S tl=selector
  99. { hd :: tl }
  100. | hd=simple_selector S? c=combinator tl=selector
  101. { hd :: c :: tl }
  102. %inline combinator:
  103. | PLUS S? { "+" }
  104. | c=COMBINATOR S? { c }
  105. simple_selector:
  106. | elem=element_name addons=element_addon*
  107. { elem ^ String.concat "" addons }
  108. | addons=element_addon+
  109. { String.concat "" addons }
  110. %inline element_addon:
  111. | a=HASH | a=cls | a=attrib | a=pseudo
  112. { a }
  113. element_name:
  114. | tag=IDENT { tag }
  115. | STAR { "*" }
  116. cls:
  117. | DOT name=IDENT
  118. { "." ^ name }
  119. attrib:
  120. | LBRACK S? left=IDENT S? right=pair(RELATION, rel_value)? RBRACK
  121. { let right = match right with None -> "" | Some (op, term) -> op ^ term in
  122. "[" ^ left ^ right ^ "]" }
  123. %inline rel_value:
  124. | S? id=IDENT S? { id }
  125. | S? s=STRING S? { s }
  126. pseudo:
  127. | COLON id=IDENT
  128. { ":" ^ id }
  129. | COLON f=FUNCTION S? arg=terminated(IDENT, S?)? RPAREN
  130. { let arg = match arg with None -> "" | Some id -> id in
  131. ":" ^ f ^ "(" ^ arg ^ ")" }
  132. declaration:
  133. | name=IDENT S? COLON S? value=expr important=boption(pair(IMPORTANT_SYM, S?))
  134. { (name, value, important) }
  135. expr:
  136. | l=exprl { concat_terms l }
  137. %inline exprl:
  138. | hd=term tl=opterm* { Term hd :: List.concat tl }
  139. %inline opterm:
  140. | t=term { [Term t] }
  141. | op=operator t=term { [Operator op; Term t] }
  142. %inline operator:
  143. | SLASH S? { "/" }
  144. | COMMA S? { "," }
  145. term:
  146. | op=unary_operator n=NUMBER S?
  147. { Unary (op, Number (n, None)) }
  148. | op=unary_operator v=UNIT_VALUE S?
  149. { let (n, u) = v in Unary (op, Number (n, Some u)) }
  150. | n=NUMBER S?
  151. { Number (n, None) }
  152. | v=UNIT_VALUE S?
  153. { let (n, u) = v in Number (n, Some u) }
  154. | str=STRING S?
  155. { Strlit str }
  156. | id=IDENT S?
  157. { Ident id }
  158. | uri=URI S?
  159. { Uri uri }
  160. | fn=FUNCTION arg=expr RPAREN S?
  161. { Function (fn, arg) }
  162. | hex=HASH S?
  163. { if Str.string_match (Str.regexp "\\d{3}\\d{3}?") hex 0
  164. then Hexcolor hex
  165. else raise (SyntaxError ("invalid color #" ^ hex)) }
  166. %inline unary_operator:
  167. | MINUS { "-" }
  168. | PLUS { "+" }